├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── kotlinCodeInsightSettings.xml ├── kotlinc.xml ├── libraries-with-intellij-classes.xml └── vcs.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASING.md ├── build-support ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── java │ └── dev │ └── drewhamilton │ └── poko │ └── build │ ├── PokoBuildExtension.kt │ ├── PokoBuildPlugin.kt │ ├── PokoSettingsPlugin.kt │ └── publish.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlin-js-store └── yarn.lock ├── poko-annotations ├── api │ └── poko-annotations.api ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── drewhamilton │ └── poko │ ├── ArrayContentBased.kt │ ├── Poko.kt │ └── optInAnnotations.kt ├── poko-compiler-plugin ├── api │ └── poko-compiler-plugin.api ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── dev │ │ └── drewhamilton │ │ └── poko │ │ ├── CompilerOptions.kt │ │ ├── PokoAnnotationNames.kt │ │ ├── PokoCommandLineProcessor.kt │ │ ├── PokoCompilerPluginRegistrar.kt │ │ ├── PokoFunction.kt │ │ ├── fir │ │ ├── PokoFirCheckersExtension.kt │ │ ├── PokoFirDeclarationGenerationExtension.kt │ │ ├── PokoFirExtensionRegistrar.kt │ │ ├── PokoFirExtensionSessionComponent.kt │ │ └── PokoKey.kt │ │ └── ir │ │ ├── PokoFunctionBodyFiller.kt │ │ ├── PokoIrGenerationExtension.kt │ │ ├── PokoMembersTransformer.kt │ │ ├── PokoOrigin.kt │ │ ├── compat.kt │ │ ├── equalsGeneration.kt │ │ ├── functionGeneration.kt │ │ ├── hashCodeGeneration.kt │ │ ├── irHelpers.kt │ │ ├── logging.kt │ │ └── toStringGeneration.kt │ └── test │ ├── kotlin │ └── dev │ │ └── drewhamilton │ │ └── poko │ │ └── PokoCompilerPluginTest.kt │ └── resources │ ├── api │ ├── DataInterface.kt │ ├── MultipleInterface.kt │ └── Primitives.kt │ └── illegal │ ├── Data.kt │ ├── GenericArrayHolder.kt │ ├── Interface.kt │ ├── NoConstructorProperties.kt │ ├── NoPrimaryConstructor.kt │ ├── NotArrayHolder.kt │ ├── OuterClass.kt │ └── Value.kt ├── poko-gradle-plugin ├── api │ └── poko-gradle-plugin.api ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── dev │ └── drewhamilton │ └── poko │ └── gradle │ ├── PokoGradlePlugin.kt │ └── PokoPluginExtension.kt ├── poko-tests ├── build.gradle.kts ├── performance │ ├── build.gradle.kts │ └── src │ │ └── test │ │ └── kotlin │ │ ├── JsPerformanceTest.kt │ │ ├── JvmPerformanceTest.kt │ │ ├── asm.kt │ │ └── sources.kt └── src │ ├── commonMain │ └── kotlin │ │ ├── Super.kt │ │ ├── data │ │ ├── AnyArrayHolder.kt │ │ ├── Complex.kt │ │ ├── ExplicitDeclarations.kt │ │ ├── Nested.kt │ │ ├── Simple.kt │ │ └── SuperclassDeclarations.kt │ │ ├── performance │ │ ├── IntAndLong.kt │ │ ├── Main.kt │ │ └── UIntAndLong.kt │ │ └── poko │ │ ├── AnyArrayHolder.kt │ │ ├── ArrayHolder.kt │ │ ├── Complex.kt │ │ ├── ComplexGenericArrayHolder.kt │ │ ├── Expected.kt │ │ ├── ExplicitDeclarations.kt │ │ ├── GenericArrayHolder.kt │ │ ├── Nested.kt │ │ ├── Simple.kt │ │ ├── SimpleWithExtraParam.kt │ │ ├── SkippedProperty.kt │ │ ├── SuperclassDeclarations.kt │ │ └── SuperclassWithFinalOverrides.kt │ ├── commonTest │ └── kotlin │ │ ├── AnyArrayHolderTest.kt │ │ ├── ArrayHolderTest.kt │ │ ├── ComplexGenericArrayHolderTest.kt │ │ ├── ComplexTest.kt │ │ ├── ExpectedTest.kt │ │ ├── ExplicitDeclarationsTest.kt │ │ ├── GenericArrayHolderTest.kt │ │ ├── NestedTest.kt │ │ ├── SimpleTest.kt │ │ ├── SimpleWithExtraParamTest.kt │ │ ├── SkippedPropertyTest.kt │ │ ├── SuperclassDeclarationsTest.kt │ │ └── SuperclassWithFinalOverridesTest.kt │ ├── jsMain │ └── kotlin │ │ └── poko │ │ └── Expected.js.kt │ ├── jvmMain │ └── kotlin │ │ └── poko │ │ └── Expected.jvm.kt │ ├── nativeMain │ └── kotlin │ │ └── poko │ │ └── Expected.native.kt │ ├── wasmJsMain │ └── kotlin │ │ └── poko │ │ └── Expected.wasmJs.kt │ └── wasmWasiMain │ └── kotlin │ └── poko │ └── Expected.wasmWasi.kt ├── renovate.json5 ├── sample ├── build.gradle.kts ├── buildSrc │ ├── build.gradle.kts │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── drewhamilton │ │ └── poko │ │ └── sample │ │ └── build │ │ └── java.kt ├── compose │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── drewhamilton │ │ │ └── poko │ │ │ └── sample │ │ │ └── compose │ │ │ ├── Poko.kt │ │ │ └── Sample.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── drewhamilton │ │ └── poko │ │ └── sample │ │ └── compose │ │ └── SampleTest.kt ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jvm │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── drewhamilton │ │ │ └── poko │ │ │ └── sample │ │ │ └── jvm │ │ │ ├── MyData.kt │ │ │ └── Sample.kt │ │ ├── test │ │ └── kotlin │ │ │ └── dev │ │ │ └── drewhamilton │ │ │ └── poko │ │ │ └── sample │ │ │ └── jvm │ │ │ └── SampleTest.kt │ │ └── testFixtures │ │ └── kotlin │ │ └── dev │ │ └── drewhamilton │ │ └── poko │ │ └── sample │ │ └── jvm │ │ └── SampleFixture.kt ├── kotlin-js-store │ └── yarn.lock ├── mpp │ ├── build.gradle.kts │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── drewhamilton │ │ │ └── poko │ │ │ └── sample │ │ │ └── mpp │ │ │ ├── ArraysSample.kt │ │ │ └── Sample.kt │ │ └── commonTest │ │ └── kotlin │ │ └── dev │ │ └── drewhamilton │ │ └── poko │ │ └── sample │ │ └── mpp │ │ ├── ArraysSampleTest.kt │ │ └── SampleTest.kt ├── properties.gradle └── settings.gradle.kts └── settings.gradle.kts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: macos-latest 11 | steps: 12 | - name: Check out the repo 13 | uses: actions/checkout@v4 14 | - name: Install JDK 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: zulu 18 | java-version: 23 19 | 20 | - name: Set up Gradle 21 | uses: gradle/actions/setup-gradle@v4 22 | 23 | - name: Build 24 | run: >- 25 | ./gradlew 26 | :poko-annotations:build 27 | :poko-compiler-plugin:build 28 | :poko-gradle-plugin:build 29 | publishToMavenLocal 30 | --stacktrace 31 | env: 32 | ORG_GRADLE_PROJECT_personalGpgKey: ${{ secrets.ORG_GRADLE_PROJECT_personalGpgKey }} 33 | ORG_GRADLE_PROJECT_personalGpgPassword: ${{ secrets.ORG_GRADLE_PROJECT_personalGpgPassword }} 34 | 35 | - name: Upload MavenLocal directory 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: MavenLocal 39 | path: ~/.m2/repository/dev/drewhamilton/poko/ 40 | if-no-files-found: error 41 | 42 | - name: Test 43 | # Builds and run tests for any not-yet-built modules, i.e. the :poko-tests modules 44 | run: ./gradlew build --stacktrace 45 | 46 | - name: Test without K2 47 | # Builds and run tests for any not-yet-built modules, i.e. the :poko-tests modules 48 | run: >- 49 | ./gradlew :poko-tests:clean build --stacktrace 50 | -Dorg.gradle.project.pokoTests.compileMode=WITHOUT_K2 51 | 52 | test-with-jdk: 53 | runs-on: ubuntu-latest 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | poko_tests_jvm_toolchain_version: [ 8, 11, 17, 21 ] 58 | steps: 59 | - name: Check out the repo 60 | uses: actions/checkout@v4 61 | 62 | - name: Install JDK ${{ matrix.poko_tests_jvm_toolchain_version }} 63 | uses: actions/setup-java@v4 64 | with: 65 | distribution: zulu 66 | java-version: | 67 | ${{ matrix.poko_tests_jvm_toolchain_version }} 68 | 23 69 | 70 | - name: Set up Gradle 71 | uses: gradle/actions/setup-gradle@v4 72 | 73 | - name: Test 74 | run: >- 75 | ./gradlew :poko-tests:jvmTest --stacktrace 76 | -Dorg.gradle.project.pokoTests.jvmToolchainVersion=${{ matrix.poko_tests_jvm_toolchain_version }} 77 | 78 | - name: Test without K2 79 | run: >- 80 | ./gradlew :poko-tests:jvmTest --stacktrace 81 | -Dorg.gradle.project.pokoTests.jvmToolchainVersion=${{ matrix.poko_tests_jvm_toolchain_version }} 82 | -Dorg.gradle.project.pokoTests.compileMode=WITHOUT_K2 83 | 84 | test-non-fir-generation: 85 | runs-on: ubuntu-latest 86 | steps: 87 | - name: Check out the repo 88 | uses: actions/checkout@v4 89 | 90 | - name: Install JDK ${{ matrix.poko_tests_jvm_toolchain_version }} 91 | uses: actions/setup-java@v4 92 | with: 93 | distribution: zulu 94 | java-version: 23 95 | 96 | - name: Set up Gradle 97 | uses: gradle/actions/setup-gradle@v4 98 | 99 | - name: Test 100 | run: ./gradlew :poko-tests:build --stacktrace -Dorg.gradle.project.pokoTests.compileMode=FIR_GENERATION_DISABLED 101 | 102 | build-sample: 103 | runs-on: ubuntu-latest 104 | needs: build 105 | strategy: 106 | fail-fast: false 107 | matrix: 108 | poko_sample_kotlin_version: [ 2.1.0, 2.1.10, ~, 2.2.0-RC ] 109 | poko_sample_kotlin_language_version: [ 1.9, ~ ] 110 | env: 111 | poko_sample_kotlin_version: ${{ matrix.poko_sample_kotlin_version }} 112 | ORG_GRADLE_PROJECT_pokoSample_kotlinLanguageVersion: ${{ matrix.poko_sample_kotlin_language_version }} 113 | steps: 114 | - name: Check out the repo 115 | uses: actions/checkout@v4 116 | - name: Install JDK 117 | uses: actions/setup-java@v4 118 | with: 119 | distribution: zulu 120 | java-version: 23 121 | - name: Download MavenLocal 122 | uses: actions/download-artifact@v4 123 | with: 124 | name: MavenLocal 125 | path: ~/.m2/repository/dev/drewhamilton/poko/ 126 | 127 | - name: Set up Gradle 128 | uses: gradle/actions/setup-gradle@v4 129 | 130 | - name: Upgrade yarn lock 131 | if: ${{ matrix.poko_sample_kotlin_version != null }} 132 | run: cd sample && ./gradlew kotlinUpgradeYarnLock 133 | 134 | - name: Build sample 135 | run: cd sample && ./gradlew build --stacktrace 136 | 137 | env: 138 | GRADLE_OPTS: >- 139 | -Dorg.gradle.configureondemand=true 140 | -Dkotlin.incremental=false 141 | -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" 142 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: [ "*" ] 7 | 8 | jobs: 9 | release: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v4 15 | 16 | - name: Install JDK 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: zulu 20 | java-version: 23 21 | 22 | - name: Set up Gradle 23 | uses: gradle/actions/setup-gradle@v4 24 | 25 | - name: Assemble for release 26 | run: ./gradlew assemble 27 | - name: Publish 28 | run: ./gradlew publish --no-configuration-cache 29 | env: 30 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_personalSonatypeIssuesUsername }} 31 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_personalSonatypeIssuesPassword }} 32 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_personalGpgKey }} 33 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ORG_GRADLE_PROJECT_personalGpgPassword }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,gradle,kotlin,intellij 3 | # Edit at https://www.gitignore.io/?templates=java,gradle,kotlin,intellij 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # Whitelisted .idea files: 10 | !.idea/codeStyles/* 11 | 12 | # User-specific stuff 13 | .idea/**/workspace.xml 14 | .idea/**/tasks.xml 15 | .idea/**/usage.statistics.xml 16 | .idea/**/dictionaries 17 | .idea/**/shelf 18 | .idea/**/jarRepositories.xml 19 | .idea/.name 20 | .idea/**/compiler.xml 21 | .idea/misc.xml 22 | .idea/other.xml 23 | .idea/caches 24 | .idea/runConfigurations.xml 25 | 26 | # Generated files 27 | .idea/**/artifacts 28 | .idea/**/contentModel.xml 29 | .idea/AndroidProjectSystem.xml 30 | 31 | # Sensitive or high-churn files 32 | .idea/**/dataSources/ 33 | .idea/**/dataSources.ids 34 | .idea/**/dataSources.local.xml 35 | .idea/**/sqlDataSources.xml 36 | .idea/**/dynamic.xml 37 | .idea/**/uiDesigner.xml 38 | .idea/**/dbnavigator.xml 39 | 40 | # Gradle 41 | .idea/**/gradle.xml 42 | .idea/**/libraries 43 | 44 | # Gradle and Maven with auto-import 45 | .idea/modules.xml 46 | .idea/*.iml 47 | .idea/modules 48 | *.iml 49 | *.ipr 50 | 51 | # CMake 52 | cmake-build-*/ 53 | 54 | # Mongo Explorer plugin 55 | .idea/**/mongoSettings.xml 56 | 57 | # File-based project format 58 | *.iws 59 | 60 | # IntelliJ 61 | out/ 62 | 63 | # mpeltonen/sbt-idea plugin 64 | .idea_modules/ 65 | 66 | # JIRA plugin 67 | atlassian-ide-plugin.xml 68 | 69 | # Cursive Clojure plugin 70 | .idea/replstate.xml 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | fabric.properties 77 | 78 | # Editor-based Rest Client 79 | .idea/httpRequests 80 | 81 | # Android studio 3.1+ serialized cache file 82 | .idea/caches/build_file_checksums.ser 83 | 84 | ### Intellij Patch ### 85 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 86 | 87 | # *.iml 88 | # modules.xml 89 | # .idea/misc.xml 90 | # *.ipr 91 | 92 | # Sonarlint plugin 93 | .idea/**/sonarlint/ 94 | 95 | # SonarQube Plugin 96 | .idea/**/sonarIssues.xml 97 | 98 | # Markdown Navigator plugin 99 | .idea/**/markdown-navigator.xml 100 | .idea/**/markdown-navigator/ 101 | 102 | ### Gradle ### 103 | .gradle 104 | build/ 105 | !**/src/**/build/ 106 | 107 | # Local configuration file (sdk path, etc) 108 | local.properties 109 | 110 | # Ignore Gradle GUI config 111 | gradle-app.setting 112 | 113 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 114 | !gradle-wrapper.jar 115 | 116 | # Cache of project 117 | .gradletasknamecache 118 | 119 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 120 | # gradle/wrapper/gradle-wrapper.properties 121 | 122 | # End of https://www.gitignore.io/api/gradle,intellij 123 | 124 | # Kotlin 125 | .kotlin 126 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 13 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/kotlinCodeInsightSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/libraries-with-intellij-classes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.18.7 4 | _2025-05-16_ 5 | 6 | Fix bug in generated `toString` for `@ReadArrayContent` properties on Poko classes targeting JS: 7 | array properties would print a value of "\[...]" instead of their actual content. 8 | 9 | Compile with Kotlin 2.1.21. Add forward compatibility with Kotlin 2.2.0-RC. Compile with Java 23. 10 | 11 | ## 0.18.6 12 | _2025-04-16_ 13 | 14 | Add forward compatibility with Kotlin 2.2.0-Beta1. 15 | 16 | ## 0.18.5 17 | _2025-03-31_ 18 | 19 | Fix compilation error when defining a Poko class with multiple super-interfaces while using 20 | `languageVersion = KotlinVersion.KOTLIN_1_9`. 21 | 22 | ## 0.18.4 23 | _2025-03-27_ 24 | 25 | Fix compilation error with `@Poko actual` classes. 26 | 27 | ## 0.18.3 28 | _2025-03-26_ 29 | 30 | Fix a bug where Poko functions declared `final` in the Poko class's supertype would be overridden. 31 | Fix compilation error when Poko functions are declared `abstract` in the Poko class's supertype(s). 32 | 33 | Under the hood, generate Poko functions with FIR-based declarations backed by IR-based function 34 | bodies. This allows K2-based IDEs to recognize the overridden functions. 35 | 36 | Compile with Kotlin 2.1.20. 37 | 38 | ## 0.18.2 39 | _2024-12-27_ 40 | 41 | Add forward compatibility with Kotlin 2.1.20-Beta1. 42 | 43 | ## 0.18.1 44 | _2024-12-13_ 45 | 46 | Add ability to skip individual properties with `@Poko.Skip`. These properties will be omitted from 47 | all generated functions. This feature is experimental and requires opt-in via `@SkipSupport`. 48 | 49 | Replace `@ArrayContentBased` with `@Poko.ReadArrayContent`. Add a deprecated `typealias` for the 50 | former to aid migration. 51 | 52 | Add support for use optional property-level features with custom Poko annotation. Nested annotations 53 | with the same name as the optional feature, such as `@MyData.ReadArrayContent` and 54 | `@MyData.Skip`, will be respected. 55 | 56 | Fix issue with FIR checkers. K2-enabled IDEs should now highlight Poko errors and warnings in 57 | consumer source code. 58 | 59 | ## 0.18.0 60 | _2024-12-2_ 61 | 62 | Compile with Kotlin 2.1.0. 63 | 64 | ## 0.17.2 65 | _2024-10-18_ 66 | 67 | Compile with Kotlin 2.0.21. Implement compatibility with Kotlin 2.0.0 – 2.1.0-Beta2. 68 | 69 | ## 0.17.1 70 | _2024-09-17_ 71 | 72 | Automatically add the Poko annotation dependency to test fixtures when they are enabled on JVM 73 | projects. 74 | 75 | ## 0.17.0 76 | _2024-08-22_ 77 | 78 | Compile with Kotlin 2.0.20. Support Java 22. 79 | 80 | ## 0.16.0 81 | _2024-05-28_ 82 | 83 | Compile with Kotlin 2.0.0. The syntax for custom annotation references now uses `/` to denote 84 | packages, e.g. `com/example/MyAnnotationClass`. 85 | 86 | Deprecate `@ArrayContentSupport` annotation and stop requiring it for using `@ArrayContentBased` 87 | annotation. 88 | 89 | ### 0.16.0-beta01 90 | _2024-05-20_ 91 | 92 | Compile with Kotlin 2.0.0-RC3. The syntax for custom annotation references now uses `/` to denote 93 | packages, e.g. `com/example/MyAnnotationClass`. 94 | 95 | ## 0.15.3 96 | _2024-05-19_ 97 | 98 | Support Android native targets and Wasm-WASI target for Kotlin Multiplatform. Implement error checks 99 | in FIR to support automatic IDE warnings in future IntelliJ releases. 100 | 101 | Compile with Kotlin 1.9.24. Use Node v22 for Wasm and JS targets. Compile released artifacts with 102 | `-Xjdk-release` flag to ensure JVM 1.8 compatibility. 103 | 104 | ## 0.15.2 105 | _2024-01-02_ 106 | 107 | Support Wasm-JS target for Kotlin Multiplatform. 108 | 109 | Compile with Kotlin 1.9.22. Support Java 21. 110 | 111 | ## 0.15.1 112 | _2023-11-27_ 113 | 114 | Compile with Kotlin 1.9.20. Support compilation with K2. Support Java 20. 115 | 116 | Optimize JS `equals` implementation as well as bytecode for `Int` and `UInt` properties. 117 | 118 | ## 0.15.0 119 | _2023-08-09_ 120 | 121 | Support Kotlin Multiplatform. 122 | 123 | Revert `poko-annotations` to an `implementation` dependency, because Kotlin/Native doesn't support 124 | `compileOnly`. The annotations are still source-retention, so the end-consumer's app binary can 125 | strip the annotations from the classpath during shrinking. 126 | 127 | Update the compiler plugin to be enabled by default and to use the default annotations by default. 128 | This brings the compiler plugin into alignment with the Gradle plugin. 129 | 130 | ## 0.14.0 131 | _2023-07-06_ 132 | 133 | Compile with Kotlin 1.9.0. 134 | 135 | Include default `poko-annotations` dependency as `compileOnly`, and convert `@Poko` annotation from 136 | binary retention to source retention. This makes Poko invisible to consumers of Poko-consuming 137 | libraries. 138 | 139 | Change the Kotlin Gradle plugin to a `compileOnly` dependency of the Poko Gradle plugin, allowing 140 | consumers to use older versions of Kotlin for their libraries than Poko uses (assuming compatible 141 | versions of the Kotlin compiler API). 142 | 143 | ## 0.13.1 144 | _2023-06-20_ 145 | 146 | Add experimental `@ArrayContentBased` annotation. This annotation can be applied to `@Poko` class 147 | array properties to ensure that the array's content is considered for `equals`, `hashCode`, and 148 | `toString`. (By default, array content is not considered.) 149 | 150 | Compile with Kotlin 1.8.22. 151 | 152 | ## 0.13.0 153 | _2023-04-03_ 154 | 155 | Support Kotlin 1.8.20. 156 | 157 | ## 0.12.0 158 | _2023-03-21_ 159 | 160 | Support Kotlin 1.8.0 and 1.8.10. Support Java 19. 161 | 162 | The `pokoAnnotation` configuration property's format changes; package names are now separated by `/` 163 | instead of `.`. For example, an inner annotation class definition would look like 164 | `"com/example/MyClass.Inner"`. 165 | 166 | The primary entry point is now `PokoCompilerPluginRegistrar`, replacing `PokoComponentRegistrar`. 167 | This change is transparent to consumers of the Gradle plugin. 168 | 169 | Automatic inclusion of the legacy `@dev.drewhamilton.extracare.DataApi` artifact is removed. The 170 | consumer can still manually depend on 171 | `implementation("dev.drewhamilton.extracare:data-api-annotations:0.6.0")` if desired. 172 | 173 | ## 0.11.0 174 | _2022-06-13_ 175 | 176 | Support Kotlin 1.7.0. Support Java 18. 177 | 178 | Drop support for non-IR compilation (which does not exist in Kotlin 1.7.0). 179 | 180 | ## 0.10.0 181 | _2022-04-15_ 182 | 183 | Support Kotlin 1.6.20. 184 | 185 | ## 0.9.0 186 | _2021-12-04_ 187 | 188 | Support Kotlin 1.6.0. 189 | 190 | ## 0.8.1 191 | _2021-06-04_ 192 | 193 | Publish a Gradle plugin marker ([#54](https://github.com/drewhamilton/Poko/issues/54)). Compile with 194 | Kotlin 1.5.10. 195 | 196 | ## 0.8.0 197 | _2021-05-18_ 198 | 199 | Support Kotlin 1.5.0. Support Java 16. 200 | 201 | ## 0.7.4 202 | _2021-04-28_ 203 | 204 | Change the default `poko-annotations` dependency to a runtime dependency when used, rather than a 205 | compile-only dependency. This ensures it is available in alternate configurations, like JVM test and 206 | Android test configurations. 207 | 208 | ## 0.7.3 209 | _2021-03-27_ 210 | 211 | Fix another bug that broke compatibility with Jetpack Compose. Poko is now compatible with Jetpack 212 | Compose 1.0.0-beta03 and Android Gradle Plugin 7.0.0-alpha12. 213 | 214 | ## 0.7.2 215 | _2021-03-21_ 216 | 217 | Enforce a minimum of one property in the primary constructor of a Poko class. Previously, 218 | compilation would succeed but could cause failures down the line with unclear error messages. 219 | 220 | ## 0.7.1 221 | _2021-02-24_ 222 | 223 | Fix a bug that broke compatibility with Jetpack Compose. Poko is now compatible with Jetpack Compose 224 | 1.0.0-alpha12. 225 | 226 | ## 0.7.0 227 | _2021-02-08_ 228 | 229 | Rename the library from "Extra Care" to "Poko". The new artifact names are `poko-compiler-plugin`, 230 | `poko-gradle-plugin`, and `poko-annotations`. The legacy `dev.drewhamilton.extracare.DataApi` 231 | is still supported if specified. 232 | 233 | ## 0.6.0 234 | _2021-02-06_ 235 | 236 | Support Kotlin 1.4.30. 237 | 238 | Add the ability to specify a custom annotation in place of the default annotation to trigger Extra 239 | Care generation on annotated classes. 240 | 241 | ## 0.5.0 242 | _2021-01-10_ 243 | 244 | Support compilation in projects where IR compilation is enabled. 245 | 246 | Disable support for `inner` and `inline` classes, which are both also not supported by the `data` 247 | keyword. 248 | 249 | Add an `extraCare` Gradle extension, where the compiler plugin can be explicitly enabled or 250 | disabled. 251 | 252 | ## 0.4.0 253 | _2021-01-01_ 254 | 255 | Support Kotlin 1.4.20 and 1.4.21. 256 | 257 | ## 0.3.1 (patched to 0.2.4) 258 | _2020-09-29_ 259 | 260 | Add code documentation to `@DataApi`. 261 | 262 | ## 0.3.0 263 | _2020-08-19_ 264 | 265 | Update to Kotlin 1.4.0. 266 | 267 | ## 0.2.3 268 | _2020-05-04_ 269 | 270 | Fix `equals` and `hashCode` generation for `long` member types. 271 | 272 | ## 0.2.2 273 | _2020-05-02_ 274 | 275 | Allow explicit `toString`, `equals`, and `hashCode` declarations in `@DataApi` classes. When one of 276 | these is declared explicitly, that function will not be generated by Extra Care. 277 | 278 | ## 0.2.1 279 | _2020-04-24_ 280 | 281 | Publish with Gradle module metadata. 282 | 283 | ## 0.2.0 284 | _2020-04-21_ 285 | 286 | Change the Gradle plugin ID so applying the plugin is less verbose: 287 | ```groovy 288 | apply plugin: 'dev.drewhamilton.extracare' 289 | ``` 290 | 291 | ## 0.1.3 292 | _2020-04-19_ 293 | 294 | Fix `equals` and `hashCode` generation for `float` and `double` member types. Update to Kotlin 295 | 1.3.72. 296 | 297 | Fix release process bug preventing changes from landing in 0.1.1 and 0.1.2. 298 | 299 | ## 0.1.2 300 | _2020-04-19_ 301 | 302 | No changes due to release process bug. 303 | 304 | ~~Fix `equals` and `hashCode` generation for `float` and `double` member types. Update to Kotlin 305 | 1.3.72.~~ 306 | 307 | ## 0.1.1 308 | _2020-03-25_ 309 | 310 | No changes due to release process bug. 311 | 312 | ~~Update to Kotlin 1.3.71.~~ 313 | 314 | ## 0.1.0 315 | _2020-03-20_ 316 | 317 | Initial release. Create a `@DataApi` class with `equals`, `hashCode`, and `toString` generated (but 318 | no `copy` or `componentN`). 319 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poko 2 | [![CI status badge](https://github.com/drewhamilton/Poko/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/drewhamilton/Poko/actions/workflows/ci.yml?query=branch%3Amain) 3 | 4 | Poko is a Kotlin compiler plugin that makes writing and maintaining data model classes for public 5 | APIs easy. Like with normal Kotlin data classes, all you have to do is provide properties in your 6 | class's primary constructor. With the `@Poko` annotation, this compiler plugin automatically 7 | generates `toString`, `equals`, and `hashCode` functions. Poko is compatible with all Kotlin 8 | Multiplatform targets. 9 | 10 | ## Use 11 | Mark your class as a `@Poko class` instead of a `data class`: 12 | ```kotlin 13 | @Poko class MyData( 14 | val int: Int, 15 | val requiredString: String, 16 | val optionalString: String?, 17 | ) 18 | ``` 19 | 20 | And reap the benefits of a readable `toString` and working `equals` and `hashCode`. Unlike normal 21 | data classes, no `copy` or `componentN` functions are generated. 22 | 23 | Like normal data classes, Poko classes must have at least one property in their primary constructor. 24 | Non-property parameters in the primary constructor are ignored, as are non-constructor properties. 25 | Any of the three generated functions can be overridden manually, in which case Poko will not 26 | generate that function but will still generate the non-overridden functions. Using array properties 27 | is not recommended, and if they are used, it is recommended to override `equals` and `hashCode` 28 | manually. 29 | 30 | ### Annotation 31 | By default, the `dev.drewhamilton.poko.Poko` annotation is used to mark classes for Poko generation. 32 | If you prefer, you can create a different annotation and supply it to the Gradle plugin. 33 | 34 | ```groovy 35 | apply plugin: "dev.drewhamilton.poko" 36 | poko { 37 | pokoAnnotation.set "com/example/MyData" 38 | } 39 | ``` 40 | 41 | Nested annotations mentioned below can optionally be added with the same name to the base annotation 42 | and used for their respective features. For example, `@MyData.ReadArrayContent` will cause the 43 | annotated property's contents to be used in the Poko-generated functions. 44 | 45 | ### Arrays 46 | By default, Poko does nothing to inspect the contents of array properties. [This aligns with the 47 | behavior of data classes](https://blog.jetbrains.com/kotlin/2015/09/feedback-request-limitations-on-data-classes/#Appendix.Comparingarrays). 48 | 49 | Poko consumers can change this behavior on a per-property basis with the `@Poko.ReadArrayContent` 50 | annotation. On properties of a typed array type, this annotation will generate a `contentDeepEquals` 51 | check. On properties of a primitive array type, this annotation will generate a `contentEquals` 52 | check. And on properties of type `Any` or of a generic type, this annotation will generate a `when` 53 | statement that disambiguates the many array types at runtime and uses the appropriate 54 | `contentDeepEquals` or `contentEquals` check. In all cases, the corresponding content-based 55 | `hashCode` and `toString` are generated for the property as well. 56 | 57 | Using arrays as properties in data types is not generally recommended: arrays are mutable, and 58 | mutating data can affect the results of `equals` and `hashCode` over time, which is generally 59 | unexpected. For this reason, `@Poko.ReadArrayContent` should only be used in very 60 | performance-sensitive APIs. 61 | 62 | ### Skipping properties 63 | 64 | It is sometimes useful to omit some properties from consideration when generating the Poko 65 | functions. This can be done with the experimental `@Poko.Skip` annotation. Properties with this 66 | annotation will be excluded from all three generated functions. For example: 67 | 68 | ```kotlin 69 | @Poko class Data( 70 | val id: String, 71 | @Poko.Skip val callback: () -> Unit, 72 | ) : CircuitUiState 73 | 74 | Data("a", { println("a") }) == Data("a", { println("not a") }) // yields `true` 75 | ``` 76 | 77 | ### Download 78 | 79 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.drewhamilton.poko/poko-compiler-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.drewhamilton.poko/poko-compiler-plugin) 80 | 81 | Poko is available on Maven Central. It is experimental, and the API may undergo breaking changes 82 | before version 1.0.0. Kotlin compiler plugins in general are experimental and new versions of Kotlin 83 | might break something in this compiler plugin. 84 | 85 | Since the Kotlin compiler has frequent breaking changes, different versions of Kotlin are 86 | exclusively compatible with specific versions of Poko. 87 | 88 | | Kotlin version | Poko version | 89 | |-----------------|--------------| 90 | | 2.1.0 – 2.1.21 | 0.18.7 | 91 | | 2.0.0 – 2.0.21 | 0.17.2 | 92 | | 1.9.0 – 1.9.24 | 0.15.3 | 93 | | 1.8.20 – 1.8.22 | 0.13.1 | 94 | | 1.8.0 – 1.8.10 | 0.12.0 | 95 | | 1.7.0 – 1.7.21 | 0.11.0 | 96 | | 1.6.20 – 1.6.21 | 0.10.0 | 97 | | 1.6.0 – 1.6.10 | 0.9.0 | 98 | | 1.5.0 – 1.5.31 | 0.8.1 | 99 | | 1.4.30 – 1.4.32 | 0.7.4 | 100 | | 1.4.20 – 1.4.21 | 0.5.0* | 101 | | 1.4.0 – 1.4.10 | 0.3.1* | 102 | | 1.3.72 | 0.2.4* | 103 | 104 | Snapshots of the development version are available in [Sonatype's Snapshots 105 | repository](https://oss.sonatype.org/#view-repositories;snapshots~browsestorage). 106 | 107 | Releases are signed with [this key](https://keyserver.ubuntu.com/pks/lookup?search=09939C73246B4BA7444CAA453D002DBC5EA9615F&fingerprint=on&op=index). 108 | ``` 109 | pub rsa4096 2020-02-02 110 | 09939C73246B4BA7444CAA453D002DBC5EA9615F 111 | uid Drew Hamilton 112 | sig 3D002DBC5EA9615F 2020-02-02 113 | ``` 114 | 115 | To use Poko, apply the Gradle plugin in your project: 116 | ```kotlin 117 | // Root project: 118 | plugins { 119 | id("dev.drewhamilton.poko") apply false 120 | } 121 | 122 | // Per module: 123 | plugins { 124 | id("dev.drewhamilton.poko") 125 | } 126 | ``` 127 | 128 | \*Versions prior to 0.7.0 use plugin name `dev.drewhamilton.extracare`. 129 | 130 | ## License 131 | ``` 132 | Copyright 2020 Drew Hamilton 133 | 134 | Licensed under the Apache License, Version 2.0 (the "License"); 135 | you may not use this file except in compliance with the License. 136 | You may obtain a copy of the License at 137 | 138 | http://www.apache.org/licenses/LICENSE-2.0 139 | 140 | Unless required by applicable law or agreed to in writing, software 141 | distributed under the License is distributed on an "AS IS" BASIS, 142 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 143 | See the License for the specific language governing permissions and 144 | limitations under the License. 145 | ``` 146 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | 1. Make sure you're on the latest commit on the main branch. 4 | 2. Update CHANGELOG.md for the impending release. 5 | 3. Change `PUBLISH_VERSION` in gradle.properties to a non-SNAPSHOT version. 6 | 4. Update README.md for the impending release. 7 | 5. Commit (don't push) the changes with message "Release x.y.z", where x.y.z is the new version. 8 | 6. Tag the commit `x.y.z`, where x.y.z is the new version. 9 | 7. Change `PUBLISH_VERSION` in gradle.properties to the next SNAPSHOT version. 10 | 8. Commit the snapshot change. 11 | 9. Push the tag and 2 commits to origin/main. 12 | 10. Wait for the "Release" Action to complete. 13 | 11. Create the release on GitHub with release notes copied from the changelog. 14 | 15 | If steps 10 fails: drop the Sonatype repo, fix the problem, delete the incorrect tag on both local 16 | and remote, and start over. 17 | -------------------------------------------------------------------------------- /build-support/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | 3 | plugins { 4 | `kotlin-dsl` 5 | `java-gradle-plugin` 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | gradlePluginPortal() 11 | } 12 | 13 | dependencies { 14 | implementation(libs.kotlin.gradle) 15 | implementation(libs.plugin.buildconfig) 16 | implementation(libs.plugin.mavenPublish) 17 | implementation(libs.plugin.dokka) 18 | } 19 | 20 | java { 21 | sourceCompatibility = JavaVersion.VERSION_11 22 | targetCompatibility = JavaVersion.VERSION_11 23 | } 24 | 25 | kotlin { 26 | compilerOptions { 27 | jvmTarget.set(JvmTarget.JVM_11) 28 | } 29 | } 30 | 31 | gradlePlugin { 32 | plugins { 33 | create("build") { 34 | id = "dev.drewhamilton.poko.build" 35 | implementationClass = "dev.drewhamilton.poko.build.PokoBuildPlugin" 36 | } 37 | create("settings") { 38 | id = "dev.drewhamilton.poko.settings" 39 | implementationClass = "dev.drewhamilton.poko.build.PokoSettingsPlugin" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /build-support/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | create("libs") { 4 | from(files("../gradle/libs.versions.toml")) 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /build-support/src/main/java/dev/drewhamilton/poko/build/PokoBuildExtension.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.build 2 | 3 | interface PokoBuildExtension { 4 | fun publishing(pomName: String) 5 | fun generateBuildConfig(basePackage: String) 6 | } 7 | -------------------------------------------------------------------------------- /build-support/src/main/java/dev/drewhamilton/poko/build/PokoBuildPlugin.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.build 2 | 3 | import com.github.gmazzo.buildconfig.BuildConfigExtension 4 | import com.vanniktech.maven.publish.MavenPublishBaseExtension 5 | import com.vanniktech.maven.publish.SonatypeHost 6 | import org.gradle.api.Action 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import org.gradle.api.plugins.AppliedPlugin 10 | import org.gradle.kotlin.dsl.buildConfigField 11 | import org.gradle.kotlin.dsl.getByType 12 | import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension 13 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask 14 | 15 | private val Project.pokoGroupId get() = property("PUBLISH_GROUP") as String 16 | private val Project.pokoVersion get() = property("PUBLISH_VERSION") as String 17 | 18 | @Suppress("unused") // Invoked reflectively by Gradle. 19 | class PokoBuildPlugin : Plugin { 20 | 21 | override fun apply(target: Project) { 22 | target.group = target.pokoGroupId 23 | target.version = target.pokoVersion 24 | 25 | target.extensions.add( 26 | PokoBuildExtension::class.java, 27 | "pokoBuild", 28 | PokoBuildExtensionImpl(target) 29 | ) 30 | 31 | commonKotlinConfiguration(target) 32 | } 33 | 34 | private fun commonKotlinConfiguration(project: Project) { 35 | project.tasks.withType(KotlinCompilationTask::class.java).configureEach { 36 | compilerOptions.progressiveMode.convention(true) 37 | } 38 | } 39 | 40 | private class PokoBuildExtensionImpl( 41 | private val project: Project, 42 | ) : PokoBuildExtension { 43 | override fun publishing(pomName: String) { 44 | project.pluginManager.apply("com.vanniktech.maven.publish") 45 | 46 | val mavenPublishing = project.extensions.getByName("mavenPublishing") as MavenPublishBaseExtension 47 | mavenPublishing.apply { 48 | coordinates(project.pokoGroupId, project.name, project.pokoVersion) 49 | 50 | pom { 51 | name.set(pomName) 52 | 53 | description.set("A Kotlin compiler plugin for generating equals, hashCode, and toString for plain old Kotlin objects.") 54 | url.set("https://github.com/drewhamilton/Poko") 55 | 56 | licenses { 57 | license { 58 | name.set("The Apache Software License, Version 2.0") 59 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 60 | distribution.set("repo") 61 | } 62 | } 63 | 64 | scm { 65 | url.set("https://github.com/drewhamilton/Poko/tree/main") 66 | connection.set("scm:git:github.com/drewhamilton/Poko.git") 67 | developerConnection.set("scm:git:ssh://github.com/drewhamilton/Poko.git") 68 | } 69 | 70 | developers { 71 | developer { 72 | id.set("drewhamilton") 73 | name.set("Drew Hamilton") 74 | email.set("software@drewhamilton.dev") 75 | } 76 | } 77 | } 78 | 79 | signAllPublications() 80 | publishToMavenCentral( 81 | host = SonatypeHost.DEFAULT, 82 | automaticRelease = true, 83 | ) 84 | } 85 | 86 | project.pluginManager.apply("org.jetbrains.dokka") 87 | project.pluginManager.apply("org.jetbrains.kotlinx.binary-compatibility-validator") 88 | 89 | // Published modules should be explicit about their API visibility. 90 | val kotlinPluginHandler = Action { 91 | val kotlin = project.extensions.getByType() 92 | kotlin.explicitApi() 93 | } 94 | project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm", kotlinPluginHandler) 95 | project.pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform", kotlinPluginHandler) 96 | } 97 | 98 | override fun generateBuildConfig(basePackage: String) { 99 | project.pluginManager.apply("com.github.gmazzo.buildconfig") 100 | 101 | val buildConfig = project.extensions.getByName("buildConfig") as BuildConfigExtension 102 | buildConfig.apply { 103 | packageName(basePackage) 104 | buildConfigField("GROUP", project.pokoGroupId) 105 | buildConfigField("VERSION", project.pokoVersion) 106 | buildConfigField("ANNOTATIONS_ARTIFACT", "poko-annotations") 107 | buildConfigField("COMPILER_PLUGIN_ARTIFACT", "poko-compiler-plugin") 108 | 109 | buildConfigField("POKO_ENABLED_OPTION_NAME", "enabled") 110 | buildConfigField("DEFAULT_POKO_ENABLED", true) 111 | 112 | buildConfigField("POKO_ANNOTATION_OPTION_NAME", "pokoAnnotation") 113 | buildConfigField("DEFAULT_POKO_ANNOTATION", "dev/drewhamilton/poko/Poko") 114 | 115 | buildConfigField("POKO_PLUGIN_ARGS_OPTION_NAME", "pokoPluginArgs") 116 | buildConfigField("POKO_PLUGIN_ARGS_LIST_DELIMITER", ';') 117 | buildConfigField("POKO_PLUGIN_ARGS_ITEM_DELIMITER", '=') 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /build-support/src/main/java/dev/drewhamilton/poko/build/PokoSettingsPlugin.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.build 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.initialization.Settings 5 | 6 | @Suppress("unused") // Invoked reflectively by Gradle. 7 | class PokoSettingsPlugin : Plugin { 8 | override fun apply(target: Settings) { 9 | target.gradle.allprojects { 10 | pluginManager.apply("dev.drewhamilton.poko.build") 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /build-support/src/main/java/dev/drewhamilton/poko/build/publish.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.build 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.kotlin.dsl.extra 5 | 6 | fun Project.setUpLocalSigning() { 7 | val isCi = System.getenv()["CI"] == "true" 8 | if (!isCi) { 9 | extra["signing.keyId"] = findProperty("personalGpgKeyId") ?: "x" 10 | extra["signing.password"] = findProperty("personalGpgPassword") ?: "x" 11 | extra["signing.secretKeyRingFile"] = findProperty("personalGpgKeyringFile") ?: "x" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.drewhamilton.poko.build.setUpLocalSigning 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 4 | 5 | plugins { 6 | // Note: kotlin-jvm and kotlin-multiplatform plugins are added implicitly via build-support 7 | 8 | alias(libs.plugins.kotlinx.binaryCompatibilityValidator) apply false 9 | alias(libs.plugins.ksp) apply false 10 | } 11 | 12 | allprojects { 13 | setUpLocalSigning() 14 | 15 | repositories { 16 | mavenCentral() 17 | 18 | // KSP: 19 | google() 20 | 21 | val snapshotsRepository = rootProject.findProperty("snapshots_repository") 22 | if (snapshotsRepository != null) { 23 | logger.lifecycle("Adding <$snapshotsRepository> repository for ${this@allprojects}") 24 | maven { url = uri(snapshotsRepository) } 25 | } 26 | val kotlinDevRepository = rootProject.findProperty("kotlin_dev_repository") 27 | if (kotlinDevRepository != null) { 28 | logger.lifecycle("Adding <$kotlinDevRepository> repository for ${this@allprojects}") 29 | maven { url = uri(kotlinDevRepository) } 30 | } 31 | } 32 | 33 | // The tests vary their own JVM targets among multiple targets. Do not overwrite them. 34 | if (path !in setOf(":poko-tests")) { 35 | val kotlinPluginHandler: AppliedPlugin.() -> Unit = { 36 | val javaVersion = JavaVersion.VERSION_1_8 37 | project.tasks.withType().configureEach { 38 | sourceCompatibility = javaVersion.toString() 39 | targetCompatibility = javaVersion.toString() 40 | } 41 | project.tasks.withType().configureEach { 42 | compilerOptions { 43 | jvmTarget.set(JvmTarget.fromTarget(javaVersion.toString())) 44 | freeCompilerArgs.add("-Xjdk-release=$javaVersion") 45 | } 46 | } 47 | } 48 | pluginManager.withPlugin("org.jetbrains.kotlin.jvm", kotlinPluginHandler) 49 | pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform", kotlinPluginHandler) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | # Do not include DOM compatibility dependency for JS targets. 4 | # See https://youtrack.jetbrains.com/issue/KT-35973 for more info. 5 | kotlin.js.stdlib.dom.api.included=false 6 | 7 | PUBLISH_GROUP=dev.drewhamilton.poko 8 | PUBLISH_VERSION=0.19.0-SNAPSHOT 9 | 10 | # Uncomment to enable snapshot dependencies: 11 | #snapshots_repository=https://oss.sonatype.org/content/repositories/snapshots 12 | # Uncomment to enable dev versions of Kotlin dependencies: 13 | #kotlin_dev_repository=https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev 14 | 15 | # Workaround for https://github.com/Kotlin/dokka/issues/1405 on `./gradlew dokkaJavadoc` 16 | org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m 17 | 18 | # Dokka v2 19 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 20 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 21 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | androidx-compose-runtime = "1.8.2" 4 | 5 | kotlin = "2.1.21" 6 | kotlinCompileTesting = "1.6.0" 7 | # https://central.sonatype.com/artifact/dev.zacsweers.kctfork/core/versions: 8 | kotlinCompileTestingFork = "0.7.1" 9 | # https://github.com/google/ksp/releases: 10 | ksp = "2.1.21-2.0.1" 11 | 12 | [libraries] 13 | 14 | androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "androidx-compose-runtime" } 15 | 16 | autoService-annotations = { module = "com.google.auto.service:auto-service-annotations", version = "1.1.1" } 17 | autoService-ksp = { module = "dev.zacsweers.autoservice:auto-service-ksp", version = "1.2.0" } 18 | 19 | junit = { module = "junit:junit", version = "4.13.2" } 20 | 21 | kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kotlinCompileTesting" } 22 | kotlinCompileTestingFork = { module = "dev.zacsweers.kctfork:core", version.ref = "kotlinCompileTestingFork" } 23 | 24 | kotlin-embeddableCompiler = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } 25 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } 26 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 27 | kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 28 | kotlin-gradleApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin" } 29 | 30 | assertk = "com.willowtreeapps.assertk:assertk:0.28.1" 31 | asm-util = "org.ow2.asm:asm-util:9.8" 32 | testParameterInjector = "com.google.testparameterinjector:test-parameter-injector:1.18" 33 | 34 | plugin-buildconfig = "com.github.gmazzo.buildconfig:plugin:5.6.5" 35 | plugin-mavenPublish = "com.vanniktech:gradle-maven-publish-plugin:0.32.0" 36 | plugin-dokka = "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0" 37 | 38 | [plugins] 39 | 40 | android-library = { id = "com.android.library", version = "8.10.1" } 41 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 42 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 43 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 44 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 45 | kotlinx-binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.17.0" } 46 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 47 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drewhamilton/Poko/15214b7c01e3e080dcba61079fc9ffdaddb775d5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /poko-annotations/api/poko-annotations.api: -------------------------------------------------------------------------------- 1 | public abstract interface annotation class dev/drewhamilton/poko/ArrayContentSupport : java/lang/annotation/Annotation { 2 | } 3 | 4 | public abstract interface annotation class dev/drewhamilton/poko/Poko : java/lang/annotation/Annotation { 5 | } 6 | 7 | public abstract interface annotation class dev/drewhamilton/poko/Poko$ReadArrayContent : java/lang/annotation/Annotation { 8 | } 9 | 10 | public abstract interface annotation class dev/drewhamilton/poko/Poko$Skip : java/lang/annotation/Annotation { 11 | } 12 | 13 | public abstract interface annotation class dev/drewhamilton/poko/SkipSupport : java/lang/annotation/Annotation { 14 | } 15 | 16 | -------------------------------------------------------------------------------- /poko-annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | 3 | plugins { 4 | id("org.jetbrains.kotlin.multiplatform") 5 | } 6 | 7 | pokoBuild { 8 | publishing("Poko Annotations") 9 | } 10 | 11 | kotlin { 12 | jvm() 13 | 14 | js().nodejs() 15 | 16 | mingwX64() 17 | 18 | linuxArm64() 19 | linuxX64() 20 | 21 | iosArm64() 22 | iosSimulatorArm64() 23 | iosX64() 24 | 25 | macosArm64() 26 | macosX64() 27 | 28 | tvosArm64() 29 | tvosX64() 30 | tvosSimulatorArm64() 31 | 32 | @OptIn(ExperimentalWasmDsl::class) 33 | wasmJs().nodejs() 34 | @OptIn(ExperimentalWasmDsl::class) 35 | wasmWasi().nodejs() 36 | 37 | watchosArm32() 38 | watchosArm64() 39 | watchosDeviceArm64() 40 | watchosSimulatorArm64() 41 | watchosX64() 42 | 43 | androidNativeArm32() 44 | androidNativeArm64() 45 | androidNativeX86() 46 | androidNativeX64() 47 | } 48 | -------------------------------------------------------------------------------- /poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/ArrayContentBased.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | /** 4 | * Legacy name for [Poko.ReadArrayContent]. 5 | */ 6 | @Deprecated( 7 | message = "Moved to @Poko.ReadArrayContent for compatibility with custom Poko annotation", 8 | replaceWith = ReplaceWith("Poko.ReadArrayContent"), 9 | ) 10 | public typealias ArrayContentBased = Poko.ReadArrayContent 11 | -------------------------------------------------------------------------------- /poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/Poko.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | /** 4 | * A `@Poko class` is similar to a `data class`: the Poko compiler plugin will generate [equals], 5 | * [hashCode], and [toString] functions for any Kotlin class marked with this annotation. Unlike 6 | * normal data classes, `copy` or `componentN` functions are not generated. This makes it easier to 7 | * maintain data models in libraries without breaking binary compatibility. 8 | * 9 | * The generated functions will be based on class properties in the primary constructor. Class 10 | * properties not in the primary constructor will not be included, and primary constructor 11 | * parameters that are not class properties will not be included. Compilation will fail if the 12 | * annotated class does not include a primary constructor. 13 | * 14 | * Each function will only be generated if it is not already manually overridden in the annotated 15 | * class. 16 | * 17 | * The annotated class cannot be a `data class`, an `inline class`, or an `inner class`. 18 | * 19 | * Like data classes, it is highly recommended that all properties used in equals/hashCode are 20 | * immutable. `var`s, mutable collections, and especially arrays should be avoided. The class itself 21 | * should also be final. The compiler plugin does not enforce these recommendations. 22 | */ 23 | @Retention(AnnotationRetention.SOURCE) 24 | @Target(AnnotationTarget.CLASS) 25 | public annotation class Poko { 26 | 27 | /** 28 | * Primary constructor properties marked with this annotation will be omitted from generated 29 | * `equals`, `hashCode`, and `toString` functions, as if they were not properties. 30 | * 31 | * This annotation has no effect on properties declared outside the primary constructor. 32 | */ 33 | @SkipSupport 34 | @Retention(AnnotationRetention.SOURCE) 35 | @Target(AnnotationTarget.PROPERTY) 36 | public annotation class Skip 37 | 38 | /** 39 | * Declares that a [Poko] class's generated functions will be based on this property's array 40 | * content. This differs from the Poko class (and data class) default of comparing arrays by 41 | * reference only. 42 | * 43 | * Poko class properties of type [Array], [BooleanArray], [CharArray], [ByteArray], [ShortArray], 44 | * [IntArray], [LongArray], [FloatArray], and [DoubleArray] are supported, including nested 45 | * [Array] types. 46 | * 47 | * Properties of a generic type or of type [Any] are also supported. For these properties, Poko will 48 | * generate a `when` statement that disambiguates the various array types at runtime and analyzes 49 | * content if the property is an array. (Note that with this logic, typed arrays will never be 50 | * considered equals to primitive arrays, even if they hold the same content. For example, 51 | * `arrayOf(1, 2)` will not be considered equals to `intArrayOf(1, 2)`.) 52 | * 53 | * Properties of a value class type that wraps an array are not supported. Tagging non-array 54 | * properties with this annotation is an error. 55 | * 56 | * Using array properties in data models is not generally recommended, because they are mutable. 57 | * Mutating an array marked with this annotation will cause the parent Poko class to produce 58 | * different `equals` and `hashCode` results at different times. This annotation should only be used 59 | * by consumers for whom performant code is more important than safe code. 60 | */ 61 | @Retention(AnnotationRetention.SOURCE) 62 | @Target(AnnotationTarget.PROPERTY) 63 | public annotation class ReadArrayContent 64 | } 65 | -------------------------------------------------------------------------------- /poko-annotations/src/commonMain/kotlin/dev/drewhamilton/poko/optInAnnotations.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | /** 4 | * Denotes an experimental API that enables the ability to skip a Poko class primary constructor 5 | * property when generating Poko functions. 6 | */ 7 | @RequiresOptIn 8 | public annotation class SkipSupport 9 | 10 | /** 11 | * Denotes an API that enables support for array content reading, which is experimental and may 12 | * change or break. 13 | */ 14 | @Deprecated("Array content support no longer requires opt-in") 15 | @RequiresOptIn 16 | public annotation class ArrayContentSupport 17 | -------------------------------------------------------------------------------- /poko-compiler-plugin/api/poko-compiler-plugin.api: -------------------------------------------------------------------------------- 1 | public final class dev/drewhamilton/poko/PokoCommandLineProcessor : org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor { 2 | public fun ()V 3 | public fun getPluginId ()Ljava/lang/String; 4 | public fun getPluginOptions ()Ljava/util/Collection; 5 | public fun processOption (Lorg/jetbrains/kotlin/compiler/plugin/AbstractCliOption;Ljava/lang/String;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V 6 | } 7 | 8 | public final class dev/drewhamilton/poko/PokoCompilerPluginRegistrar : org/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar { 9 | public fun ()V 10 | public fun getSupportsK2 ()Z 11 | public fun registerExtensions (Lorg/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar$ExtensionStorage;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V 12 | } 13 | 14 | -------------------------------------------------------------------------------- /poko-compiler-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("org.jetbrains.kotlin.jvm") 5 | alias(libs.plugins.ksp) 6 | } 7 | 8 | pokoBuild { 9 | publishing("Poko Compiler Plugin") 10 | generateBuildConfig("dev.drewhamilton.poko") 11 | } 12 | 13 | dependencies { 14 | // The stdlib and compiler APIs will be provided by the enclosing Kotlin compiler environment. 15 | compileOnly(libs.kotlin.stdlib) 16 | compileOnly(libs.kotlin.embeddableCompiler) 17 | 18 | compileOnly(libs.autoService.annotations) 19 | ksp(libs.autoService.ksp) 20 | 21 | testImplementation(project(":poko-annotations")) 22 | testImplementation(libs.kotlin.embeddableCompiler) 23 | testImplementation(libs.junit) 24 | testImplementation(libs.assertk) 25 | testImplementation(libs.testParameterInjector) 26 | testImplementation(libs.kotlinCompileTestingFork) 27 | } 28 | 29 | tasks.withType().configureEach { 30 | compilerOptions { 31 | freeCompilerArgs.add("-Xcontext-receivers") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /poko-compiler-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | # We want the stdlib as a compileOnly dependency. 2 | kotlin.stdlib.default.dependency=false 3 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/CompilerOptions.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 4 | 5 | internal object CompilerOptions { 6 | val ENABLED = CompilerConfigurationKey(BuildConfig.POKO_ENABLED_OPTION_NAME) 7 | val POKO_ANNOTATION = CompilerConfigurationKey(BuildConfig.POKO_ANNOTATION_OPTION_NAME) 8 | val POKO_PLUGIN_ARGS = 9 | CompilerConfigurationKey>(BuildConfig.POKO_PLUGIN_ARGS_OPTION_NAME) 10 | } 11 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/PokoAnnotationNames.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | import org.jetbrains.kotlin.name.Name 4 | 5 | internal object PokoAnnotationNames { 6 | val ReadArrayContent = Name.identifier("ReadArrayContent") 7 | val Skip = Name.identifier("Skip") 8 | } 9 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/PokoCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | import com.google.auto.service.AutoService 4 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 5 | import org.jetbrains.kotlin.compiler.plugin.CliOption 6 | import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor 7 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 8 | import org.jetbrains.kotlin.config.CompilerConfiguration 9 | 10 | @AutoService(CommandLineProcessor::class) 11 | @ExperimentalCompilerApi 12 | public class PokoCommandLineProcessor : CommandLineProcessor { 13 | 14 | override val pluginId: String = BuildConfig.COMPILER_PLUGIN_ARTIFACT 15 | 16 | override val pluginOptions: Collection = listOf( 17 | CliOption( 18 | optionName = CompilerOptions.ENABLED.toString(), 19 | valueDescription = "", 20 | description = "", 21 | required = false, 22 | ), 23 | CliOption( 24 | optionName = CompilerOptions.POKO_ANNOTATION.toString(), 25 | valueDescription = "Annotation class name", 26 | description = "", 27 | required = false, 28 | ), 29 | CliOption( 30 | optionName = CompilerOptions.POKO_PLUGIN_ARGS.toString(), 31 | valueDescription = "", 32 | description = "Additional Poko compiler plugin arguments", 33 | required = false, 34 | ), 35 | ) 36 | 37 | override fun processOption( 38 | option: AbstractCliOption, 39 | value: String, 40 | configuration: CompilerConfiguration 41 | ): Unit = when (option.optionName) { 42 | CompilerOptions.ENABLED.toString() -> 43 | configuration.put(CompilerOptions.ENABLED, value.toBoolean()) 44 | CompilerOptions.POKO_ANNOTATION.toString() -> 45 | configuration.put(CompilerOptions.POKO_ANNOTATION, value) 46 | CompilerOptions.POKO_PLUGIN_ARGS.toString() -> 47 | configuration.put( 48 | CompilerOptions.POKO_PLUGIN_ARGS, 49 | value.split(BuildConfig.POKO_PLUGIN_ARGS_LIST_DELIMITER) 50 | .associate { arg -> 51 | arg.split(BuildConfig.POKO_PLUGIN_ARGS_ITEM_DELIMITER).let { 52 | require(it.size == 2) { 53 | "Invalid syntax for <${it.firstOrNull()}>: " + 54 | "must be in `key=value` property format" 55 | } 56 | it.first() to it.last() 57 | } 58 | }, 59 | ) 60 | else -> 61 | throw IllegalArgumentException("Unknown plugin option: ${option.optionName}") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/PokoCompilerPluginRegistrar.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | import com.google.auto.service.AutoService 4 | import dev.drewhamilton.poko.BuildConfig.DEFAULT_POKO_ANNOTATION 5 | import dev.drewhamilton.poko.BuildConfig.DEFAULT_POKO_ENABLED 6 | import dev.drewhamilton.poko.fir.PokoFirExtensionRegistrar 7 | import dev.drewhamilton.poko.ir.PokoIrGenerationExtension 8 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 9 | import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity 10 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 11 | import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar 12 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 13 | import org.jetbrains.kotlin.config.CommonConfigurationKeys 14 | import org.jetbrains.kotlin.config.CompilerConfiguration 15 | import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter 16 | import org.jetbrains.kotlin.name.ClassId 17 | 18 | @ExperimentalCompilerApi 19 | @AutoService(CompilerPluginRegistrar::class) 20 | public class PokoCompilerPluginRegistrar : CompilerPluginRegistrar() { 21 | 22 | override val supportsK2: Boolean get() = true 23 | 24 | private val firDeclarationGenerationPluginArg = 25 | "poko.experimental.enableFirDeclarationGeneration" 26 | private val knownPokoPluginArgs = setOf( 27 | firDeclarationGenerationPluginArg, 28 | ) 29 | 30 | override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { 31 | if (!configuration.get(CompilerOptions.ENABLED, DEFAULT_POKO_ENABLED)) 32 | return 33 | 34 | val pokoAnnotationString = configuration.get(CompilerOptions.POKO_ANNOTATION, DEFAULT_POKO_ANNOTATION) 35 | val pokoAnnotationClassId = ClassId.fromString(pokoAnnotationString) 36 | 37 | val messageCollector = configuration.get( 38 | CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, 39 | MessageCollector.NONE, 40 | ) 41 | 42 | val pokoPluginArgs = configuration.get(CompilerOptions.POKO_PLUGIN_ARGS, emptyMap()) 43 | pokoPluginArgs.keys.forEach { pluginArgName -> 44 | if (!knownPokoPluginArgs.contains(pluginArgName)) { 45 | messageCollector.report( 46 | severity = CompilerMessageSeverity.WARNING, 47 | message = "Ignoring unknown Poko plugin arg: $pluginArgName", 48 | ) 49 | } 50 | } 51 | 52 | val firDeclarationGenerationPluginValue = pokoPluginArgs[firDeclarationGenerationPluginArg] 53 | val firDeclarationGenerationEnabled = 54 | firDeclarationGenerationPluginValue?.equals("false", ignoreCase = true)?.not()?.also { 55 | messageCollector.report( 56 | severity = CompilerMessageSeverity.WARNING, 57 | message = "<$firDeclarationGenerationPluginArg> resolved to $it. " + 58 | "This experimental flag may disappear at any time.", 59 | ) 60 | } ?: true 61 | 62 | IrGenerationExtension.registerExtension( 63 | PokoIrGenerationExtension( 64 | pokoAnnotationName = pokoAnnotationClassId, 65 | firDeclarationGeneration = firDeclarationGenerationEnabled, 66 | messageCollector = messageCollector, 67 | ) 68 | ) 69 | 70 | FirExtensionRegistrarAdapter.registerExtension( 71 | PokoFirExtensionRegistrar( 72 | pokoAnnotation = pokoAnnotationClassId, 73 | declarationGeneration = firDeclarationGenerationEnabled, 74 | ) 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/PokoFunction.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko 2 | 3 | import org.jetbrains.kotlin.name.Name 4 | import org.jetbrains.kotlin.util.OperatorNameConventions 5 | 6 | /** 7 | * Exhaustive representation of all functions Poko generates. 8 | */ 9 | internal enum class PokoFunction( 10 | val functionName: Name, 11 | ) { 12 | Equals(OperatorNameConventions.EQUALS), 13 | HashCode(OperatorNameConventions.HASH_CODE), 14 | ToString(OperatorNameConventions.TO_STRING), 15 | } 16 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/fir/PokoFirDeclarationGenerationExtension.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.fir 2 | 3 | import dev.drewhamilton.poko.PokoFunction 4 | import dev.drewhamilton.poko.PokoFunction.Equals 5 | import dev.drewhamilton.poko.PokoFunction.HashCode 6 | import dev.drewhamilton.poko.PokoFunction.ToString 7 | import org.jetbrains.kotlin.descriptors.ClassKind 8 | import org.jetbrains.kotlin.descriptors.Modality 9 | import org.jetbrains.kotlin.descriptors.Visibilities 10 | import org.jetbrains.kotlin.fir.FirSession 11 | import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction 12 | import org.jetbrains.kotlin.fir.declarations.utils.isFinal 13 | import org.jetbrains.kotlin.fir.declarations.utils.visibility 14 | import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension 15 | import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar 16 | import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext 17 | import org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate 18 | import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider 19 | import org.jetbrains.kotlin.fir.plugin.createMemberFunction 20 | import org.jetbrains.kotlin.fir.resolve.toClassSymbol 21 | import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol 22 | import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol 23 | import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol 24 | import org.jetbrains.kotlin.fir.symbols.impl.isExtension 25 | import org.jetbrains.kotlin.fir.types.ConeKotlinType 26 | import org.jetbrains.kotlin.name.CallableId 27 | import org.jetbrains.kotlin.name.Name 28 | import org.jetbrains.kotlin.utils.addToStdlib.runIf 29 | 30 | internal class PokoFirDeclarationGenerationExtension( 31 | session: FirSession, 32 | ) : FirDeclarationGenerationExtension(session) { 33 | private val pokoAnnotation by lazy { 34 | session.pokoFirExtensionSessionComponent.pokoAnnotation 35 | } 36 | 37 | private val pokoAnnotationPredicate by lazy { 38 | LookupPredicate.create { 39 | annotated(pokoAnnotation.asSingleFqName()) 40 | } 41 | } 42 | 43 | /** 44 | * Pairs of . 45 | */ 46 | private val pokoClasses by lazy { 47 | session.predicateBasedProvider.getSymbolsByPredicate(pokoAnnotationPredicate) 48 | .filterIsInstance() 49 | } 50 | 51 | override fun FirDeclarationPredicateRegistrar.registerPredicates() { 52 | register(pokoAnnotationPredicate) 53 | } 54 | 55 | override fun getCallableNamesForClass( 56 | classSymbol: FirClassSymbol<*>, 57 | context: MemberGenerationContext, 58 | ): Set = when { 59 | classSymbol in pokoClasses -> PokoFunction.entries.map { it.functionName }.toSet() 60 | else -> emptySet() 61 | } 62 | 63 | override fun generateFunctions( 64 | callableId: CallableId, 65 | context: MemberGenerationContext? 66 | ): List { 67 | val owner = context?.owner ?: return emptyList() 68 | 69 | val callableName = callableId.callableName 70 | val function = when (callableName) { 71 | Equals.functionName -> runIf(owner.canGenerateFunction(Equals)) { 72 | createEqualsFunction(owner) 73 | } 74 | 75 | HashCode.functionName -> runIf(owner.canGenerateFunction(HashCode)) { 76 | createHashCodeFunction(owner) 77 | } 78 | 79 | ToString.functionName -> runIf(owner.canGenerateFunction(ToString)) { 80 | createToStringFunction(owner) 81 | } 82 | 83 | else -> null 84 | } 85 | return function?.let { listOf(it.symbol) } ?: emptyList() 86 | } 87 | 88 | private fun FirClassSymbol<*>.canGenerateFunction(function: PokoFunction): Boolean { 89 | if (hasDeclaredFunction(function)) return false 90 | 91 | val superclassFunction = findNearestSuperclassFunction(function) 92 | 93 | return superclassFunction?.isOverridable ?: true 94 | } 95 | 96 | private fun FirClassSymbol<*>.hasDeclaredFunction(function: PokoFunction): Boolean { 97 | return declaredFunction(function) != null 98 | } 99 | 100 | /** 101 | * Recursively finds the [function] in this class's nearest superclass with the same signature. 102 | * Ignores super-interfaces. 103 | */ 104 | private fun FirClassSymbol<*>.findNearestSuperclassFunction( 105 | function: PokoFunction, 106 | ): FirNamedFunctionSymbol? { 107 | val superclass = resolvedSuperTypes 108 | .mapNotNull { it.toClassSymbol(session) } 109 | .filter { it.classKind == ClassKind.CLASS } 110 | .apply { check(size < 2) { "Found multiple superclasses" } } 111 | .singleOrNull() 112 | ?: return null 113 | 114 | return superclass.declaredFunction(function) 115 | ?: superclass.findNearestSuperclassFunction(function) 116 | } 117 | 118 | /** 119 | * Finds the function symbol if this [function] is declared in this class. 120 | */ 121 | private fun FirClassSymbol<*>.declaredFunction( 122 | function: PokoFunction, 123 | ): FirNamedFunctionSymbol? { 124 | return declarationSymbols 125 | .filterIsInstance() 126 | .filter { functionSymbol -> 127 | !functionSymbol.isExtension && 128 | functionSymbol.name == function.functionName && 129 | functionSymbol.valueParameterSymbols 130 | .map { it.resolvedReturnType } == function.valueParameterTypes() 131 | } 132 | .apply { check(size < 2) { "Found multiple identical functions" } } 133 | .singleOrNull() 134 | } 135 | 136 | private fun PokoFunction.valueParameterTypes(): List = when (this) { 137 | Equals -> listOf(session.builtinTypes.nullableAnyType.coneType) 138 | HashCode -> emptyList() 139 | ToString -> emptyList() 140 | } 141 | 142 | private val FirNamedFunctionSymbol.isOverridable: Boolean 143 | get() = visibility != Visibilities.Private && !isFinal 144 | 145 | private fun createEqualsFunction( 146 | owner: FirClassSymbol<*>, 147 | ): FirSimpleFunction = createMemberFunction( 148 | owner = owner, 149 | key = PokoKey, 150 | name = Equals.functionName, 151 | returnType = session.builtinTypes.booleanType.coneType, 152 | ) { 153 | modality = Modality.OPEN 154 | status { 155 | isOperator = true 156 | } 157 | valueParameter( 158 | name = Name.identifier("other"), 159 | type = session.builtinTypes.nullableAnyType.coneType, 160 | key = PokoKey, 161 | ) 162 | } 163 | 164 | private fun createHashCodeFunction( 165 | owner: FirClassSymbol<*>, 166 | ): FirSimpleFunction = createMemberFunction( 167 | owner = owner, 168 | key = PokoKey, 169 | name = HashCode.functionName, 170 | returnType = session.builtinTypes.intType.coneType, 171 | ) { 172 | modality = Modality.OPEN 173 | } 174 | 175 | private fun createToStringFunction( 176 | owner: FirClassSymbol<*>, 177 | ): FirSimpleFunction = createMemberFunction( 178 | owner = owner, 179 | key = PokoKey, 180 | name = ToString.functionName, 181 | returnType = session.builtinTypes.stringType.coneType, 182 | ) { 183 | modality = Modality.OPEN 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/fir/PokoFirExtensionRegistrar.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.fir 2 | 3 | import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar 4 | import org.jetbrains.kotlin.name.ClassId 5 | 6 | internal class PokoFirExtensionRegistrar( 7 | private val pokoAnnotation: ClassId, 8 | private val declarationGeneration: Boolean, 9 | ) : FirExtensionRegistrar() { 10 | override fun ExtensionRegistrarContext.configurePlugin() { 11 | +PokoFirExtensionSessionComponent.getFactory(pokoAnnotation) 12 | +::PokoFirCheckersExtension 13 | if (declarationGeneration) { 14 | +::PokoFirDeclarationGenerationExtension 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/fir/PokoFirExtensionSessionComponent.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.fir 2 | 3 | import dev.drewhamilton.poko.PokoAnnotationNames 4 | import org.jetbrains.kotlin.fir.FirSession 5 | import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent 6 | import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent.Factory 7 | import org.jetbrains.kotlin.name.ClassId 8 | 9 | internal class PokoFirExtensionSessionComponent( 10 | session: FirSession, 11 | internal val pokoAnnotation: ClassId, 12 | ) : FirExtensionSessionComponent(session) { 13 | 14 | internal val pokoSkipAnnotation: ClassId = 15 | pokoAnnotation.createNestedClassId(PokoAnnotationNames.Skip) 16 | 17 | internal companion object { 18 | internal fun getFactory(pokoAnnotation: ClassId): Factory { 19 | return Factory { session -> 20 | PokoFirExtensionSessionComponent(session, pokoAnnotation) 21 | } 22 | } 23 | } 24 | } 25 | 26 | internal val FirSession.pokoFirExtensionSessionComponent: PokoFirExtensionSessionComponent by FirSession.sessionComponentAccessor() 27 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/fir/PokoKey.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.fir 2 | 3 | import org.jetbrains.kotlin.GeneratedDeclarationKey 4 | 5 | internal object PokoKey : GeneratedDeclarationKey() { 6 | override fun toString() = "FirPoko" 7 | } 8 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoFunctionBodyFiller.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import dev.drewhamilton.poko.PokoFunction 4 | import dev.drewhamilton.poko.fir.PokoKey 5 | import org.jetbrains.kotlin.GeneratedDeclarationKey 6 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 7 | import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder 8 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 9 | import org.jetbrains.kotlin.ir.IrElement 10 | import org.jetbrains.kotlin.ir.builders.irBlockBody 11 | import org.jetbrains.kotlin.ir.declarations.IrDeclaration 12 | import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin.GeneratedByPlugin 13 | import org.jetbrains.kotlin.ir.declarations.IrFile 14 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 15 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 16 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 17 | import org.jetbrains.kotlin.ir.util.isEquals 18 | import org.jetbrains.kotlin.ir.util.isHashCode 19 | import org.jetbrains.kotlin.ir.util.isToString 20 | import org.jetbrains.kotlin.ir.util.parentAsClass 21 | import org.jetbrains.kotlin.ir.visitors.IrVisitorVoid 22 | import org.jetbrains.kotlin.name.ClassId 23 | 24 | @OptIn(UnsafeDuringIrConstructionAPI::class) 25 | internal class PokoFunctionBodyFiller( 26 | private val pokoAnnotation: ClassId, 27 | private val context: IrPluginContext, 28 | private val messageCollector: MessageCollector, 29 | ) : IrVisitorVoid() { 30 | 31 | override fun visitSimpleFunction(declaration: IrSimpleFunction) { 32 | val origin = declaration.origin 33 | if (origin !is GeneratedByPlugin || !interestedIn(origin.pluginKey)) { 34 | return 35 | } 36 | 37 | require(declaration.body == null) 38 | 39 | val pokoFunction = when { 40 | declaration.isEquals() -> PokoFunction.Equals 41 | declaration.isHashCode() -> PokoFunction.HashCode 42 | declaration.isToString() -> PokoFunction.ToString 43 | else -> return 44 | } 45 | 46 | val pokoClass = declaration.parentAsClass 47 | val pokoProperties = pokoClass.pokoProperties(pokoAnnotation).also { 48 | if (it.isEmpty()) { 49 | messageCollector.log("No primary constructor properties") 50 | messageCollector.reportErrorOnClass( 51 | irClass = pokoClass, 52 | message = "Poko class primary constructor must have at least one not-skipped property", 53 | ) 54 | } 55 | } 56 | 57 | declaration.body = DeclarationIrBuilder( 58 | generatorContext = context, 59 | symbol = declaration.symbol, 60 | ).irBlockBody { 61 | when (pokoFunction) { 62 | PokoFunction.Equals -> generateEqualsMethodBody( 63 | pokoAnnotation = pokoAnnotation, 64 | context = this@PokoFunctionBodyFiller.context, 65 | irClass = pokoClass, 66 | functionDeclaration = declaration, 67 | classProperties = pokoProperties, 68 | messageCollector = messageCollector, 69 | ) 70 | 71 | PokoFunction.HashCode -> generateHashCodeMethodBody( 72 | pokoAnnotation = pokoAnnotation, 73 | context = this@PokoFunctionBodyFiller.context, 74 | functionDeclaration = declaration, 75 | classProperties = pokoProperties, 76 | messageCollector = messageCollector, 77 | ) 78 | 79 | PokoFunction.ToString -> generateToStringMethodBody( 80 | pokoAnnotation = pokoAnnotation, 81 | context = this@PokoFunctionBodyFiller.context, 82 | irClass = pokoClass, 83 | functionDeclaration = declaration, 84 | classProperties = pokoProperties, 85 | messageCollector = messageCollector, 86 | ) 87 | } 88 | } 89 | } 90 | 91 | private fun interestedIn( 92 | key: GeneratedDeclarationKey?, 93 | ): Boolean { 94 | return key == PokoKey 95 | } 96 | 97 | override fun visitElement(element: IrElement) { 98 | when (element) { 99 | is IrDeclaration, is IrFile, is IrModuleFragment -> element.acceptChildrenVoidCompat(this) 100 | else -> Unit 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoIrGenerationExtension.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 4 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 5 | import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity 6 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 7 | import org.jetbrains.kotlin.cli.common.messages.MessageUtil 8 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 9 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 10 | import org.jetbrains.kotlin.name.ClassId 11 | 12 | internal class PokoIrGenerationExtension( 13 | private val pokoAnnotationName: ClassId, 14 | private val firDeclarationGeneration: Boolean, 15 | private val messageCollector: MessageCollector 16 | ) : IrGenerationExtension { 17 | 18 | override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { 19 | if (pluginContext.referenceClass(pokoAnnotationName) == null) { 20 | moduleFragment.reportError("Could not find class <$pokoAnnotationName>") 21 | return 22 | } 23 | 24 | if (firDeclarationGeneration && pluginContext.afterK2) { 25 | val bodyFiller = PokoFunctionBodyFiller( 26 | pokoAnnotation = pokoAnnotationName, 27 | context = pluginContext, 28 | messageCollector = messageCollector, 29 | ) 30 | moduleFragment.acceptChildrenVoidCompat(bodyFiller) 31 | } else { 32 | val pokoMembersTransformer = PokoMembersTransformer( 33 | pokoAnnotationName = pokoAnnotationName, 34 | pluginContext = pluginContext, 35 | messageCollector = messageCollector, 36 | ) 37 | moduleFragment.transform(pokoMembersTransformer, null) 38 | } 39 | } 40 | 41 | private fun IrModuleFragment.reportError(message: String) { 42 | val psi = descriptor.findPsi() 43 | val location = MessageUtil.psiElementToMessageLocation(psi) 44 | messageCollector.report(CompilerMessageSeverity.ERROR, message, location) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/PokoOrigin.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import kotlin.properties.ReadOnlyProperty 4 | import kotlin.reflect.KProperty 5 | import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin 6 | 7 | internal object PokoOrigin : IrDeclarationOrigin, ReadOnlyProperty { 8 | override val name: String = "GENERATED_POKO_CLASS_MEMBER" 9 | 10 | override fun toString(): String = name 11 | 12 | override fun getValue(thisRef: Any?, property: KProperty<*>): PokoOrigin = this 13 | } 14 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/equalsGeneration.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 4 | import org.jetbrains.kotlin.backend.common.lower.irNot 5 | import org.jetbrains.kotlin.builtins.PrimitiveType 6 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 7 | import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder 8 | import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope 9 | import org.jetbrains.kotlin.ir.builders.irEquals 10 | import org.jetbrains.kotlin.ir.builders.irIfThenReturnFalse 11 | import org.jetbrains.kotlin.ir.builders.irIfThenReturnTrue 12 | import org.jetbrains.kotlin.ir.builders.irReturnTrue 13 | import org.jetbrains.kotlin.ir.builders.irTemporary 14 | import org.jetbrains.kotlin.ir.declarations.IrClass 15 | import org.jetbrains.kotlin.ir.declarations.IrFunction 16 | import org.jetbrains.kotlin.ir.declarations.IrProperty 17 | import org.jetbrains.kotlin.ir.expressions.IrBranch 18 | import org.jetbrains.kotlin.ir.expressions.IrExpression 19 | import org.jetbrains.kotlin.ir.symbols.IrClassSymbol 20 | import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol 21 | import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol 22 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 23 | import org.jetbrains.kotlin.ir.types.classifierOrFail 24 | import org.jetbrains.kotlin.ir.types.classifierOrNull 25 | import org.jetbrains.kotlin.ir.util.defaultType 26 | import org.jetbrains.kotlin.ir.util.isArrayOrPrimitiveArray 27 | import org.jetbrains.kotlin.ir.util.render 28 | import org.jetbrains.kotlin.name.CallableId 29 | import org.jetbrains.kotlin.name.ClassId 30 | import org.jetbrains.kotlin.name.FqName 31 | import org.jetbrains.kotlin.name.Name 32 | 33 | /** 34 | * Generate the body of the equals method. Adapted from 35 | * [org.jetbrains.kotlin.ir.util.DataClassMembersGenerator.MemberFunctionBuilder.generateEqualsMethodBody]. 36 | */ 37 | internal fun IrBlockBodyBuilder.generateEqualsMethodBody( 38 | pokoAnnotation: ClassId, 39 | context: IrPluginContext, 40 | irClass: IrClass, 41 | functionDeclaration: IrFunction, 42 | classProperties: List, 43 | messageCollector: MessageCollector, 44 | ) { 45 | val irType = irClass.defaultType 46 | fun irOther(): IrExpression = IrGetValueImpl( 47 | parameter = functionDeclaration.regularParametersCompat.single(), 48 | ) 49 | 50 | +irIfThenReturnTrue(irEqeqeqCompat(receiver(functionDeclaration), irOther())) 51 | +irIfThenReturnFalse(irNotIsCompat(irOther(), irType)) 52 | 53 | val otherWithCast = irTemporary(irImplicitCastCompat(irOther(), irType), "other_with_cast") 54 | for (property in classProperties) { 55 | val field = property.backingField!! 56 | val arg1 = irGetFieldCompat(receiver(functionDeclaration), field) 57 | val arg2 = irGetFieldCompat(irGetCompat(irType, otherWithCast.symbol), field) 58 | val irNotEquals = when { 59 | property.hasReadArrayContentAnnotation(pokoAnnotation) -> { 60 | irNot( 61 | irArrayContentDeepEquals( 62 | context = context, 63 | receiver = arg1, 64 | argument = arg2, 65 | property = property, 66 | messageCollector = messageCollector, 67 | ), 68 | ) 69 | } 70 | 71 | else -> { 72 | irNotEqualsCompat(arg1, arg2) 73 | } 74 | } 75 | +irIfThenReturnFalse(irNotEquals) 76 | } 77 | +irReturnTrue() 78 | } 79 | 80 | /** 81 | * Generates IR code that checks the equality of [receiver] and [argument] by content. If [property] 82 | * type is not an array type, but may be an array at runtime, generates a runtime type check. 83 | */ 84 | private fun IrBuilderWithScope.irArrayContentDeepEquals( 85 | context: IrPluginContext, 86 | receiver: IrExpression, 87 | argument: IrExpression, 88 | property: IrProperty, 89 | messageCollector: MessageCollector, 90 | ): IrExpression { 91 | val propertyType = property.type 92 | val propertyClassifier = propertyType.classifierOrFail 93 | 94 | val isArray = propertyClassifier.isArrayOrPrimitiveArray(context.irBuiltIns) 95 | if (!isArray) { 96 | val mayBeRuntimeArray = propertyClassifier.mayBeRuntimeArray(context) 97 | return if (mayBeRuntimeArray) { 98 | irRuntimeArrayContentDeepEquals(context, receiver, argument) 99 | } else { 100 | messageCollector.reportErrorOnProperty( 101 | property = property, 102 | message = "@ArrayContentBased on property of type <${propertyType.render()}> not supported", 103 | ) 104 | irEquals(receiver, argument) 105 | } 106 | } 107 | 108 | return irCallContentDeepEquals( 109 | context = context, 110 | classifier = propertyClassifier, 111 | receiver = receiver, 112 | argument = argument, 113 | ) 114 | } 115 | 116 | /** 117 | * Generates IR code that checks the type of [receiver] at runtime, and performs an array content 118 | * equality check against [argument] if the type is an array type. 119 | */ 120 | private fun IrBuilderWithScope.irRuntimeArrayContentDeepEquals( 121 | context: IrPluginContext, 122 | receiver: IrExpression, 123 | argument: IrExpression, 124 | ): IrExpression { 125 | return irWhenCompat( 126 | type = context.irBuiltIns.booleanType, 127 | branches = listOf( 128 | irArrayTypeCheckAndContentDeepEqualsBranch( 129 | context = context, 130 | receiver = receiver, 131 | argument = argument, 132 | classSymbol = context.irBuiltIns.arrayClass, 133 | ), 134 | 135 | // Map each primitive type to a `when` branch covering its respective primitive array 136 | // type: 137 | *PrimitiveType.entries.map { primitiveType -> 138 | irArrayTypeCheckAndContentDeepEqualsBranch( 139 | context = context, 140 | receiver = receiver, 141 | argument = argument, 142 | classSymbol = primitiveType.toPrimitiveArrayClassSymbol(context), 143 | ) 144 | }.toTypedArray(), 145 | 146 | irElseBranchCompat( 147 | irEqualsCompat(receiver, argument), 148 | ), 149 | ), 150 | ) 151 | } 152 | 153 | /** 154 | * Generates a runtime `when` branch checking for content deep equality of [receiver] and 155 | * [argument]. The branch is only executed if [receiver] is an instance of [classSymbol]. 156 | */ 157 | private fun IrBuilderWithScope.irArrayTypeCheckAndContentDeepEqualsBranch( 158 | context: IrPluginContext, 159 | receiver: IrExpression, 160 | argument: IrExpression, 161 | classSymbol: IrClassSymbol, 162 | ): IrBranch { 163 | val type = classSymbol.createArrayType(context) 164 | return irBranchCompat( 165 | condition = irIsCompat(receiver, type), 166 | result = irIfThenElseCompat( 167 | type = context.irBuiltIns.booleanType, 168 | condition = irIsCompat(argument, type), 169 | thenPart = irCallContentDeepEquals( 170 | context = context, 171 | classifier = classSymbol, 172 | receiver = irImplicitCastCompat(receiver, type), 173 | argument = irImplicitCastCompat(argument, type), 174 | ), 175 | elsePart = irFalseCompat(), 176 | ), 177 | ) 178 | } 179 | 180 | private fun IrBuilderWithScope.irCallContentDeepEquals( 181 | context: IrPluginContext, 182 | classifier: IrClassifierSymbol, 183 | receiver: IrExpression, 184 | argument: IrExpression, 185 | ): IrExpression { 186 | return irCallCompat( 187 | callee = findContentDeepEqualsFunctionSymbol(context, classifier), 188 | type = context.irBuiltIns.booleanType, 189 | typeArgumentsCount = 1, 190 | ).apply { 191 | extensionReceiver = receiver 192 | putValueArgument(0, argument) 193 | } 194 | } 195 | 196 | /** 197 | * Finds `contentDeepEquals` function if [classifier] represents a typed array, or `contentEquals` 198 | * function if it represents a primitive array. 199 | */ 200 | @OptIn(UnsafeDuringIrConstructionAPI::class) 201 | private fun findContentDeepEqualsFunctionSymbol( 202 | context: IrPluginContext, 203 | classifier: IrClassifierSymbol, 204 | ): IrSimpleFunctionSymbol { 205 | val callableName = if (classifier == context.irBuiltIns.arrayClass) { 206 | "contentDeepEquals" 207 | } else { 208 | "contentEquals" 209 | } 210 | return context.referenceFunctions( 211 | callableId = CallableId( 212 | packageName = FqName("kotlin.collections"), 213 | callableName = Name.identifier(callableName), 214 | ), 215 | ).single { functionSymbol -> 216 | // Find the single function with the relevant array type and disambiguate against the 217 | // older non-nullable receiver overload: 218 | functionSymbol.owner.extensionReceiverParameter?.type?.let { 219 | it.classifierOrNull == classifier && it.isNullableCompat() 220 | } ?: false 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/functionGeneration.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import dev.drewhamilton.poko.PokoAnnotationNames 4 | import org.jetbrains.kotlin.builtins.PrimitiveType 5 | import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder 6 | import org.jetbrains.kotlin.ir.builders.IrGeneratorContextInterface 7 | import org.jetbrains.kotlin.ir.declarations.IrClass 8 | import org.jetbrains.kotlin.ir.declarations.IrFunction 9 | import org.jetbrains.kotlin.ir.declarations.IrProperty 10 | import org.jetbrains.kotlin.ir.declarations.IrTypeParameter 11 | import org.jetbrains.kotlin.ir.declarations.IrValueParameter 12 | import org.jetbrains.kotlin.ir.expressions.IrGetValue 13 | import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl 14 | import org.jetbrains.kotlin.ir.symbols.IrClassSymbol 15 | import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol 16 | import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol 17 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 18 | import org.jetbrains.kotlin.ir.types.IrSimpleType 19 | import org.jetbrains.kotlin.ir.types.classOrNull 20 | import org.jetbrains.kotlin.ir.types.classifierOrNull 21 | import org.jetbrains.kotlin.ir.types.createType 22 | import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl 23 | import org.jetbrains.kotlin.ir.util.hasAnnotation 24 | import org.jetbrains.kotlin.ir.util.isAnnotationClass 25 | import org.jetbrains.kotlin.ir.util.isInterface 26 | import org.jetbrains.kotlin.ir.util.render 27 | import org.jetbrains.kotlin.name.ClassId 28 | 29 | /** 30 | * The type of an [IrProperty]. 31 | */ 32 | internal val IrProperty.type 33 | get() = backingField?.type 34 | ?: getter?.returnType 35 | ?: error("Can't find type of ${render()}") 36 | 37 | /** 38 | * The receiver value (i.e. `this`) for a function with a dispatch (i.e. non-extension) receiver. 39 | * 40 | * In the context of Poko, only works properly after the overridden method has had its 41 | * `dispatchReceiverParameter` updated to the current parent class. 42 | */ 43 | internal fun IrBlockBodyBuilder.receiver( 44 | function: IrFunction, 45 | ): IrGetValue = IrGetValueImpl(function.dispatchReceiverParameter!!) 46 | 47 | /** 48 | * Gets the value of the given [parameter]. 49 | */ 50 | internal fun IrBlockBodyBuilder.IrGetValueImpl( 51 | parameter: IrValueParameter, 52 | ): IrGetValueImpl { 53 | return IrGetValueImpl( 54 | startOffset = startOffset, 55 | endOffset = endOffset, 56 | type = parameter.type, 57 | symbol = parameter.symbol, 58 | ) 59 | } 60 | 61 | internal fun IrProperty.hasReadArrayContentAnnotation( 62 | pokoAnnotation: ClassId, 63 | ): Boolean = hasAnnotation( 64 | classId = pokoAnnotation.createNestedClassId(PokoAnnotationNames.ReadArrayContent), 65 | ) 66 | 67 | /** 68 | * Returns true if the classifier represents a type that may be an array at runtime (e.g. [Any] or 69 | * a generic type). 70 | */ 71 | internal fun IrClassifierSymbol?.mayBeRuntimeArray( 72 | context: IrGeneratorContextInterface, 73 | ): Boolean { 74 | return this == context.irBuiltIns.anyClass || 75 | (this is IrTypeParameterSymbol && hasArrayOrPrimitiveArrayUpperBound(context)) 76 | } 77 | 78 | private fun IrTypeParameterSymbol.hasArrayOrPrimitiveArrayUpperBound( 79 | context: IrGeneratorContextInterface, 80 | ): Boolean { 81 | superTypesCompat().forEach { superType -> 82 | val superTypeClassifier = superType.classifierOrNull 83 | // Note: A generic type cannot have an array as an upper bound, else that would also 84 | // be checked here. 85 | val foundUpperBoundMatch = superTypeClassifier == context.irBuiltIns.anyClass || 86 | (superTypeClassifier is IrTypeParameterSymbol && 87 | superTypeClassifier.hasArrayOrPrimitiveArrayUpperBound(context)) 88 | 89 | if (foundUpperBoundMatch) { 90 | return true 91 | } 92 | } 93 | return false 94 | } 95 | 96 | @OptIn(UnsafeDuringIrConstructionAPI::class) 97 | internal val IrTypeParameter.erasedUpperBound: IrClass 98 | get() { 99 | // Pick the (necessarily unique) non-interface upper bound if it exists 100 | for (type in superTypes) { 101 | val irClass = type.classOrNull?.owner ?: continue 102 | if (!irClass.isInterface && !irClass.isAnnotationClass) return irClass 103 | } 104 | 105 | // Otherwise, choose either the first IrClass supertype or recurse. 106 | // In the first case, all supertypes are interface types and the choice was arbitrary. 107 | // In the second case, there is only a single supertype. 108 | return when (val firstSuper = superTypes.first().classifierOrNull?.owner) { 109 | is IrClass -> firstSuper 110 | is IrTypeParameter -> @Suppress("RecursivePropertyAccessor") firstSuper.erasedUpperBound 111 | else -> error("unknown supertype kind $firstSuper") 112 | } 113 | } 114 | 115 | internal fun PrimitiveType.toPrimitiveArrayClassSymbol( 116 | context: IrGeneratorContextInterface, 117 | ): IrClassSymbol { 118 | return context.irBuiltIns.primitiveTypesToPrimitiveArrays.getValue(this) 119 | } 120 | 121 | internal fun IrClassSymbol.createArrayType( 122 | context: IrGeneratorContextInterface, 123 | ): IrSimpleType { 124 | val typeArguments = when { 125 | this == context.irBuiltIns.arrayClass -> listOf(IrStarProjectionImpl) 126 | this in context.irBuiltIns.primitiveArraysToPrimitiveTypes -> emptyList() 127 | else -> throw IllegalArgumentException("$this is not an array class symbol") 128 | } 129 | return createType(hasQuestionMark = false, arguments = typeArguments) 130 | } 131 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/irHelpers.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import dev.drewhamilton.poko.PokoAnnotationNames 4 | import org.jetbrains.kotlin.KtFakeSourceElementKind 5 | import org.jetbrains.kotlin.fir.backend.FirMetadataSource 6 | import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI 7 | import org.jetbrains.kotlin.ir.declarations.IrClass 8 | import org.jetbrains.kotlin.ir.declarations.IrProperty 9 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 10 | import org.jetbrains.kotlin.ir.util.hasAnnotation 11 | import org.jetbrains.kotlin.ir.util.properties 12 | import org.jetbrains.kotlin.name.ClassId 13 | import org.jetbrains.kotlin.psi.KtParameter 14 | import org.jetbrains.kotlin.resolve.source.getPsi 15 | 16 | @UnsafeDuringIrConstructionAPI 17 | internal fun IrClass.pokoProperties( 18 | pokoAnnotation: ClassId, 19 | ): List { 20 | return properties 21 | .toList() 22 | .filter { 23 | val metadata = it.metadata 24 | if (metadata is FirMetadataSource.Property) { 25 | // Using K2: 26 | metadata.fir.source?.kind is KtFakeSourceElementKind.PropertyFromParameter 27 | } else { 28 | // Not using K2: 29 | @OptIn(ObsoleteDescriptorBasedAPI::class) 30 | it.symbol.descriptor.source.getPsi() is KtParameter 31 | } 32 | } 33 | .filter { 34 | !it.hasAnnotation( 35 | classId = pokoAnnotation.createNestedClassId(PokoAnnotationNames.Skip), 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/logging.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity 4 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 5 | import org.jetbrains.kotlin.cli.common.messages.MessageUtil 6 | import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI 7 | import org.jetbrains.kotlin.ir.declarations.IrClass 8 | import org.jetbrains.kotlin.ir.declarations.IrProperty 9 | import org.jetbrains.kotlin.resolve.source.getPsi 10 | 11 | internal fun MessageCollector.log(message: String) { 12 | report(CompilerMessageSeverity.LOGGING, "POKO COMPILER PLUGIN (IR): $message") 13 | } 14 | 15 | internal fun MessageCollector.reportErrorOnClass(irClass: IrClass, message: String) { 16 | val psi = irClass.source.getPsi() 17 | val location = MessageUtil.psiElementToMessageLocation(psi) 18 | report(CompilerMessageSeverity.ERROR, message, location) 19 | } 20 | 21 | // TODO: Implement an FIR-based declaration checker: 22 | @OptIn(ObsoleteDescriptorBasedAPI::class) 23 | internal fun MessageCollector.reportErrorOnProperty(property: IrProperty, message: String) { 24 | val psi = property.descriptor.source.getPsi() 25 | val location = MessageUtil.psiElementToMessageLocation(psi) 26 | report(CompilerMessageSeverity.ERROR, message, location) 27 | } 28 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/main/kotlin/dev/drewhamilton/poko/ir/toStringGeneration.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.ir 2 | 3 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 4 | import org.jetbrains.kotlin.builtins.PrimitiveType 5 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector 6 | import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder 7 | import org.jetbrains.kotlin.ir.builders.irReturn 8 | import org.jetbrains.kotlin.ir.declarations.IrClass 9 | import org.jetbrains.kotlin.ir.declarations.IrFunction 10 | import org.jetbrains.kotlin.ir.declarations.IrProperty 11 | import org.jetbrains.kotlin.ir.expressions.IrBranch 12 | import org.jetbrains.kotlin.ir.expressions.IrExpression 13 | import org.jetbrains.kotlin.ir.expressions.addArgument 14 | import org.jetbrains.kotlin.ir.symbols.IrClassSymbol 15 | import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol 16 | import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol 17 | import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI 18 | import org.jetbrains.kotlin.ir.types.classifierOrFail 19 | import org.jetbrains.kotlin.ir.types.classifierOrNull 20 | import org.jetbrains.kotlin.ir.util.isArrayOrPrimitiveArray 21 | import org.jetbrains.kotlin.ir.util.render 22 | import org.jetbrains.kotlin.name.CallableId 23 | import org.jetbrains.kotlin.name.ClassId 24 | import org.jetbrains.kotlin.name.FqName 25 | import org.jetbrains.kotlin.name.Name 26 | 27 | /** 28 | * Generate the body of the toString method. Adapted from 29 | * [org.jetbrains.kotlin.ir.util.DataClassMembersGenerator.MemberFunctionBuilder.generateToStringMethodBody]. 30 | */ 31 | internal fun IrBlockBodyBuilder.generateToStringMethodBody( 32 | pokoAnnotation: ClassId, 33 | context: IrPluginContext, 34 | irClass: IrClass, 35 | functionDeclaration: IrFunction, 36 | classProperties: List, 37 | messageCollector: MessageCollector, 38 | ) { 39 | val irConcat = irConcatCompat() 40 | irConcat.addArgument(irStringCompat(irClass.name.asString() + "(")) 41 | 42 | var first = true 43 | for (property in classProperties) { 44 | if (!first) irConcat.addArgument(irStringCompat(", ")) 45 | 46 | irConcat.addArgument(irStringCompat(property.name.asString() + "=")) 47 | 48 | val propertyValue = irGetFieldCompat(receiver(functionDeclaration), property.backingField!!) 49 | 50 | val classifier = property.type.classifierOrNull 51 | val hasArrayContentBasedAnnotation = property.hasReadArrayContentAnnotation(pokoAnnotation) 52 | val propertyStringValue = when { 53 | hasArrayContentBasedAnnotation && classifier.mayBeRuntimeArray(context) -> { 54 | val field = property.backingField!! 55 | val instance = irGetFieldCompat(receiver(functionDeclaration), field) 56 | irRuntimeArrayContentDeepToString(context, instance) 57 | } 58 | 59 | hasArrayContentBasedAnnotation -> { 60 | val toStringFunctionSymbol = maybeFindArrayDeepToStringFunction( 61 | context = context, 62 | property = property, 63 | messageCollector = messageCollector 64 | ) ?: context.irBuiltIns.dataClassArrayMemberToStringSymbol 65 | irCallToStringFunction( 66 | toStringFunctionSymbol = toStringFunctionSymbol, 67 | value = propertyValue, 68 | ) 69 | } 70 | 71 | classifier.isArrayOrPrimitiveArray(context.irBuiltIns) -> { 72 | irCallToStringFunction( 73 | toStringFunctionSymbol = context.irBuiltIns.dataClassArrayMemberToStringSymbol, 74 | value = propertyValue, 75 | ) 76 | } 77 | 78 | else -> propertyValue 79 | } 80 | 81 | irConcat.addArgument(propertyStringValue) 82 | first = false 83 | } 84 | irConcat.addArgument(irStringCompat(")")) 85 | +irReturn(irConcat) 86 | } 87 | 88 | /** 89 | * Returns `contentDeepToString` function symbol if it is an appropriate option for [property], 90 | * else returns null. 91 | */ 92 | private fun maybeFindArrayDeepToStringFunction( 93 | context: IrPluginContext, 94 | property: IrProperty, 95 | messageCollector: MessageCollector, 96 | ): IrSimpleFunctionSymbol? { 97 | val propertyClassifier = property.type.classifierOrFail 98 | 99 | val isArray = propertyClassifier.isArrayOrPrimitiveArray(context.irBuiltIns) 100 | if (!isArray) { 101 | messageCollector.reportErrorOnProperty( 102 | property = property, 103 | message = "@ArrayContentBased on property of type <${property.type.render()}> not supported", 104 | ) 105 | return null 106 | } 107 | 108 | return findContentDeepToStringFunctionSymbol(context, propertyClassifier) 109 | } 110 | 111 | /** 112 | * Generates a `when` branch that checks the runtime type of the [value] instance and invokes 113 | * `contentDeepToString` or `contentToString` for typed arrays and primitive arrays, respectively. 114 | */ 115 | private fun IrBlockBodyBuilder.irRuntimeArrayContentDeepToString( 116 | context: IrPluginContext, 117 | value: IrExpression, 118 | ): IrExpression { 119 | return irWhenCompat( 120 | type = context.irBuiltIns.stringType, 121 | branches = listOf( 122 | irArrayTypeCheckAndContentDeepToStringBranch( 123 | context = context, 124 | value = value, 125 | classSymbol = context.irBuiltIns.arrayClass, 126 | ), 127 | 128 | // Map each primitive type to a `when` branch covering its respective primitive array 129 | // type: 130 | *PrimitiveType.entries.map { primitiveType -> 131 | irArrayTypeCheckAndContentDeepToStringBranch( 132 | context = context, 133 | value = value, 134 | classSymbol = primitiveType.toPrimitiveArrayClassSymbol(context), 135 | ) 136 | }.toTypedArray(), 137 | 138 | irElseBranchCompat( 139 | irCallToStringFunction( 140 | toStringFunctionSymbol = context.irBuiltIns.extensionToString, 141 | value = value, 142 | ), 143 | ), 144 | ), 145 | ) 146 | } 147 | 148 | /** 149 | * Generates a runtime `when` branch computing the content deep toString of [value]. The branch is 150 | * only executed if [value] is an instance of [classSymbol]. 151 | */ 152 | private fun IrBlockBodyBuilder.irArrayTypeCheckAndContentDeepToStringBranch( 153 | context: IrPluginContext, 154 | value: IrExpression, 155 | classSymbol: IrClassSymbol, 156 | ): IrBranch { 157 | val type = classSymbol.createArrayType(context) 158 | return irBranchCompat( 159 | condition = irIsCompat(value, type), 160 | result = irCallToStringFunction( 161 | toStringFunctionSymbol = findContentDeepToStringFunctionSymbol(context, classSymbol), 162 | value = irImplicitCastCompat(value, type), 163 | ), 164 | ) 165 | } 166 | 167 | /** 168 | * Finds `contentDeepToString` function if [propertyClassifier] is a typed array, or 169 | * `contentToString` function if it is a primitive array. 170 | */ 171 | @OptIn(UnsafeDuringIrConstructionAPI::class) 172 | private fun findContentDeepToStringFunctionSymbol( 173 | context: IrPluginContext, 174 | propertyClassifier: IrClassifierSymbol, 175 | ): IrSimpleFunctionSymbol { 176 | val callableName = if (propertyClassifier == context.irBuiltIns.arrayClass) { 177 | "contentDeepToString" 178 | } else { 179 | "contentToString" 180 | } 181 | return context.referenceFunctions( 182 | callableId = CallableId( 183 | packageName = FqName("kotlin.collections"), 184 | callableName = Name.identifier(callableName), 185 | ), 186 | ).single { functionSymbol -> 187 | // Find the single function with the relevant array type and disambiguate against the 188 | // older non-nullable receiver overload: 189 | functionSymbol.owner.extensionReceiverParameter?.type?.let { 190 | it.classifierOrNull == propertyClassifier && it.isNullableCompat() 191 | } ?: false 192 | } 193 | } 194 | 195 | @OptIn(UnsafeDuringIrConstructionAPI::class) 196 | private fun IrBlockBodyBuilder.irCallToStringFunction( 197 | toStringFunctionSymbol: IrSimpleFunctionSymbol, 198 | value: IrExpression, 199 | ): IrExpression { 200 | return irCallCompat( 201 | callee = toStringFunctionSymbol, 202 | type = context.irBuiltIns.stringType, 203 | ).apply { 204 | // Poko modification: check for extension receiver for contentDeepToString 205 | val hasExtensionReceiver = 206 | toStringFunctionSymbol.owner.extensionReceiverParameter != null 207 | if (hasExtensionReceiver) { 208 | extensionReceiver = value 209 | } else { 210 | putValueArgument(0, value) 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/api/DataInterface.kt: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | interface DataInterface { 6 | val id: String 7 | 8 | override fun equals(other: Any?): Boolean 9 | override fun hashCode(): Int 10 | } 11 | 12 | @Poko class MyData( 13 | override val id: String, 14 | ) : DataInterface 15 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/api/MultipleInterface.kt: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko 6 | class MultipleInterface( 7 | val int: Int, 8 | val requiredString: String, 9 | val optionalString: String?, 10 | ): FunctionalInterface, MarkerInterface { 11 | override fun getAnInt(): Int = int 12 | } 13 | 14 | interface FunctionalInterface { 15 | fun getAnInt(): Int 16 | } 17 | 18 | interface MarkerInterface 19 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/api/Primitives.kt: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class Primitives( 7 | val string: String, 8 | val float: Float, 9 | val double: Double, 10 | val long: Long, 11 | val int: Int, 12 | val short: Short, 13 | val byte: Byte, 14 | val boolean: Boolean 15 | ) 16 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/Data.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko data class Data( 7 | val string: String, 8 | val float: Float, 9 | val double: Double, 10 | val long: Long, 11 | val int: Int, 12 | val short: Short, 13 | val byte: Byte, 14 | val boolean: Boolean 15 | ) 16 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/GenericArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.ArrayContentBased 4 | import dev.drewhamilton.poko.Poko 5 | 6 | @Suppress("Unused") 7 | @Poko class GenericArrayHolder>( 8 | @ArrayContentBased val generic: G, 9 | ) 10 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/Interface.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko interface Interface 7 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/NoConstructorProperties.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused", "UNUSED_PARAMETER") 6 | @Poko class NoConstructorProperties( 7 | nonProperty: String, 8 | ) { 9 | val nonParameter: String = "" 10 | } 11 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/NoPrimaryConstructor.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko class NoPrimaryConstructor { 7 | @Suppress("ConvertSecondaryConstructorToPrimary", "UNUSED_PARAMETER") 8 | constructor(string: String) 9 | } 10 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/NotArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.ArrayContentBased 4 | import dev.drewhamilton.poko.Poko 5 | 6 | @Suppress("Unused") 7 | @Poko class NotArrayHolder( 8 | @ArrayContentBased val string: String, 9 | @ArrayContentBased val int: Int, 10 | @ArrayContentBased val float: Float, 11 | ) 12 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/OuterClass.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | class OuterClass { 7 | 8 | @Poko inner class Inner( 9 | val value: String 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /poko-compiler-plugin/src/test/resources/illegal/Value.kt: -------------------------------------------------------------------------------- 1 | package illegal 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko @JvmInline value class Value( 7 | val id: String 8 | ) 9 | -------------------------------------------------------------------------------- /poko-gradle-plugin/api/poko-gradle-plugin.api: -------------------------------------------------------------------------------- 1 | public final class dev/drewhamilton/poko/gradle/PokoGradlePlugin : org/jetbrains/kotlin/gradle/plugin/KotlinCompilerPluginSupportPlugin { 2 | public fun ()V 3 | public synthetic fun apply (Ljava/lang/Object;)V 4 | public fun apply (Lorg/gradle/api/Project;)V 5 | public fun applyToCompilation (Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;)Lorg/gradle/api/provider/Provider; 6 | public fun getCompilerPluginId ()Ljava/lang/String; 7 | public fun getPluginArtifact ()Lorg/jetbrains/kotlin/gradle/plugin/SubpluginArtifact; 8 | public fun getPluginArtifactForNative ()Lorg/jetbrains/kotlin/gradle/plugin/SubpluginArtifact; 9 | public fun isApplicable (Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;)Z 10 | } 11 | 12 | public abstract class dev/drewhamilton/poko/gradle/PokoPluginExtension { 13 | public fun (Lorg/gradle/api/model/ObjectFactory;)V 14 | public final fun getEnabled ()Lorg/gradle/api/provider/Property; 15 | public final fun getPokoAnnotation ()Lorg/gradle/api/provider/Property; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /poko-gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-gradle-plugin` 3 | id("org.jetbrains.kotlin.jvm") 4 | } 5 | 6 | pokoBuild { 7 | publishing("Poko Gradle Plugin") 8 | generateBuildConfig("dev.drewhamilton.poko.gradle") 9 | } 10 | 11 | gradlePlugin { 12 | plugins { 13 | create("poko") { 14 | id = "dev.drewhamilton.poko" 15 | implementationClass = "dev.drewhamilton.poko.gradle.PokoGradlePlugin" 16 | } 17 | } 18 | } 19 | 20 | tasks.validatePlugins { 21 | enableStricterValidation.set(true) 22 | } 23 | 24 | dependencies { 25 | compileOnly(libs.kotlin.gradleApi) 26 | 27 | testImplementation(libs.junit) 28 | testImplementation(libs.assertk) 29 | } 30 | -------------------------------------------------------------------------------- /poko-gradle-plugin/src/main/kotlin/dev/drewhamilton/poko/gradle/PokoGradlePlugin.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.gradle 2 | 3 | import dev.drewhamilton.poko.gradle.BuildConfig.DEFAULT_POKO_ANNOTATION 4 | import org.gradle.api.Project 5 | import org.gradle.api.plugins.JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME 6 | import org.gradle.api.provider.Provider 7 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 8 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin 9 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME 10 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer 11 | import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact 12 | import org.jetbrains.kotlin.gradle.plugin.SubpluginOption 13 | 14 | public class PokoGradlePlugin : KotlinCompilerPluginSupportPlugin { 15 | 16 | override fun apply(target: Project) { 17 | val extension = target.extensions.create("poko", PokoPluginExtension::class.java) 18 | 19 | target.afterEvaluate { 20 | val annotationDependency = when (extension.pokoAnnotation.get()) { 21 | DEFAULT_POKO_ANNOTATION -> BuildConfig.annotationsDependency 22 | else -> null 23 | } 24 | if (annotationDependency != null) { 25 | if (target.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")) { 26 | val kotlin = target.extensions.getByName("kotlin") as KotlinSourceSetContainer 27 | kotlin.sourceSets.getByName(COMMON_MAIN_SOURCE_SET_NAME) { sourceSet -> 28 | sourceSet.dependencies { 29 | implementation(annotationDependency) 30 | } 31 | } 32 | } else { 33 | if (target.plugins.hasPlugin("org.gradle.java-test-fixtures")) { 34 | target.dependencies.add("testFixturesImplementation", annotationDependency) 35 | } 36 | target.dependencies.add(IMPLEMENTATION_CONFIGURATION_NAME, annotationDependency) 37 | } 38 | } 39 | } 40 | } 41 | 42 | override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true 43 | 44 | override fun getCompilerPluginId(): String = BuildConfig.COMPILER_PLUGIN_ARTIFACT 45 | 46 | override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( 47 | groupId = BuildConfig.GROUP, 48 | artifactId = getCompilerPluginId(), 49 | version = BuildConfig.VERSION 50 | ) 51 | 52 | override fun applyToCompilation( 53 | kotlinCompilation: KotlinCompilation<*>, 54 | ): Provider> { 55 | val project = kotlinCompilation.target.project 56 | val extension = project.extensions.getByType(PokoPluginExtension::class.java) 57 | 58 | val pokoPluginArgs = project.properties 59 | .filter { it.key.startsWith("poko.", ignoreCase = true) } 60 | .map { (key, value) -> "$key${BuildConfig.POKO_PLUGIN_ARGS_ITEM_DELIMITER}$value" } 61 | .joinToString(separator = BuildConfig.POKO_PLUGIN_ARGS_LIST_DELIMITER.toString()) 62 | .ifBlank { null } 63 | 64 | return project.provider { 65 | listOfNotNull( 66 | SubpluginOption( 67 | key = BuildConfig.POKO_ENABLED_OPTION_NAME, 68 | value = extension.enabled.get().toString(), 69 | ), 70 | SubpluginOption( 71 | key = BuildConfig.POKO_ANNOTATION_OPTION_NAME, 72 | value = extension.pokoAnnotation.get(), 73 | ), 74 | pokoPluginArgs?.let { 75 | SubpluginOption( 76 | key = BuildConfig.POKO_PLUGIN_ARGS_OPTION_NAME, 77 | value = it, 78 | ) 79 | }, 80 | ) 81 | } 82 | } 83 | 84 | private val BuildConfig.annotationsDependency: String 85 | get() = "$GROUP:$ANNOTATIONS_ARTIFACT:$VERSION" 86 | } 87 | -------------------------------------------------------------------------------- /poko-gradle-plugin/src/main/kotlin/dev/drewhamilton/poko/gradle/PokoPluginExtension.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.gradle 2 | 3 | import dev.drewhamilton.poko.gradle.BuildConfig.DEFAULT_POKO_ANNOTATION 4 | import dev.drewhamilton.poko.gradle.BuildConfig.DEFAULT_POKO_ENABLED 5 | import javax.inject.Inject 6 | import org.gradle.api.model.ObjectFactory 7 | import org.gradle.api.provider.Property 8 | 9 | public abstract class PokoPluginExtension @Inject constructor(objects: ObjectFactory) { 10 | 11 | public val enabled: Property = objects.property(Boolean::class.javaObjectType) 12 | .convention(DEFAULT_POKO_ENABLED) 13 | 14 | /** 15 | * Define a custom Poko marker annotation. The poko-annotations artifact won't be automatically 16 | * added as a dependency if a different annotation is defined. 17 | * 18 | * Note that this must be in the format of a string where packages are delimited by `/` and 19 | * classes by `.`, e.g. `com/example/Nested.Annotation`. 20 | * 21 | * Note that this only affects the main Poko annotation. Additional Poko annotations, such as 22 | * `@ArrayContentBased`, are not customizable. If a custom Poko marker annotation is defined 23 | * _and_ additional Poko annotations are used, the poko-annotations artifact must be manually 24 | * added as an `implementation` dependency. 25 | */ 26 | public val pokoAnnotation: Property = objects.property(String::class.java) 27 | .convention(DEFAULT_POKO_ANNOTATION) 28 | } 29 | -------------------------------------------------------------------------------- /poko-tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | import org.jetbrains.kotlin.gradle.dsl.KotlinVersion 3 | import org.jetbrains.kotlin.gradle.plugin.NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME 4 | import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME 5 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 6 | 7 | plugins { 8 | id("org.jetbrains.kotlin.multiplatform") 9 | } 10 | 11 | val compileMode = findProperty("pokoTests.compileMode") 12 | when (compileMode) { 13 | null -> Unit // Nothing to configure 14 | 15 | "WITHOUT_K2" -> { 16 | logger.lifecycle("Building :poko-tests without K2 (language level 1.9)") 17 | tasks.withType().configureEach { 18 | compilerOptions { 19 | languageVersion = KotlinVersion.KOTLIN_1_9 20 | progressiveMode = false 21 | } 22 | } 23 | } 24 | 25 | "FIR_GENERATION_DISABLED" -> { 26 | logger.lifecycle("Building :poko-tests with FIR declaration generation disabled") 27 | tasks.withType().configureEach { 28 | compilerOptions { 29 | freeCompilerArgs.addAll( 30 | listOf( 31 | "-P", 32 | "plugin:poko-compiler-plugin:pokoPluginArgs=poko.experimental.enableFirDeclarationGeneration=false", 33 | ), 34 | ) 35 | } 36 | } 37 | } 38 | 39 | else -> throw IllegalArgumentException("Unknown pokoTests.compileMode: <$compileMode>") 40 | } 41 | 42 | val jvmToolchainVersion = (findProperty("pokoTests.jvmToolchainVersion") as? String)?.toInt() 43 | 44 | kotlin { 45 | compilerOptions { 46 | freeCompilerArgs.add("-Xexpect-actual-classes") 47 | } 48 | 49 | jvmToolchainVersion?.let { jvmToolchain(it) } 50 | 51 | jvm() 52 | 53 | js { 54 | nodejs() 55 | // Produce a JS file for performance tests. 56 | binaries.executable() 57 | } 58 | 59 | mingwX64() 60 | 61 | linuxArm64() 62 | linuxX64() 63 | 64 | iosArm64() 65 | iosSimulatorArm64() 66 | iosX64() 67 | 68 | macosArm64() 69 | macosX64() 70 | 71 | tvosArm64() 72 | tvosX64() 73 | tvosSimulatorArm64() 74 | 75 | @OptIn(ExperimentalWasmDsl::class) 76 | wasmJs().nodejs() 77 | @OptIn(ExperimentalWasmDsl::class) 78 | wasmWasi().nodejs() 79 | 80 | watchosArm32() 81 | watchosArm64() 82 | watchosDeviceArm64() 83 | watchosSimulatorArm64() 84 | watchosX64() 85 | 86 | androidNativeArm32() 87 | androidNativeArm64() 88 | 89 | androidNativeX86() 90 | androidNativeX64() 91 | 92 | sourceSets { 93 | commonMain { 94 | dependencies { 95 | implementation(project(":poko-annotations")) 96 | } 97 | } 98 | commonTest { 99 | dependencies { 100 | implementation(libs.kotlin.test) 101 | implementation(libs.assertk) 102 | } 103 | } 104 | } 105 | } 106 | 107 | dependencies { 108 | add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, project(":poko-compiler-plugin")) 109 | add(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, project(":poko-compiler-plugin")) 110 | } 111 | -------------------------------------------------------------------------------- /poko-tests/performance/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.jvm") 3 | } 4 | 5 | dependencies { 6 | testImplementation(libs.junit) 7 | testImplementation(libs.assertk) 8 | testImplementation(libs.asm.util) 9 | } 10 | 11 | tasks.named("test") { 12 | dependsOn(":poko-tests:compileProductionExecutableKotlinJs") 13 | dependsOn(":poko-tests:compileKotlinJvm") 14 | } 15 | -------------------------------------------------------------------------------- /poko-tests/performance/src/test/kotlin/JsPerformanceTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import assertk.assertAll 3 | import assertk.assertThat 4 | import assertk.assertions.hasSize 5 | import assertk.assertions.isEmpty 6 | import org.junit.Test 7 | 8 | class JsPerformanceTest { 9 | @Test fun `equals does not perform redundant instanceof check`() { 10 | val javascript = jsOutput().readText() 11 | 12 | // Hack to filter out data classes, which do have the `THROW_CCE` code: 13 | val intAndLongLines = javascript.split("\n").filter { it.contains("IntAndLong") } 14 | assertAll { 15 | assertThat( 16 | actual = intAndLongLines.filter { it.contains("other instanceof IntAndLong") }, 17 | ).hasSize(1) 18 | 19 | assertThat( 20 | actual = intAndLongLines.filter { it.contains("THROW_CCE") }, 21 | ).isEmpty() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /poko-tests/performance/src/test/kotlin/JvmPerformanceTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertThat 3 | import assertk.assertions.contains 4 | import assertk.assertions.doesNotContain 5 | import org.junit.AssumptionViolatedException 6 | import org.junit.Test 7 | import org.objectweb.asm.ClassReader 8 | 9 | class JvmPerformanceTest { 10 | @Test fun `int property does not emit hashCode method invocation`() { 11 | val classfile = jvmOutput("performance/IntAndLong.class") 12 | val bytecode = bytecodeToText(classfile.readBytes()) 13 | assertThat(bytecode).all { 14 | contains("java/lang/Long.hashCode") 15 | doesNotContain("java/lang/Integer.hashCode") 16 | } 17 | } 18 | 19 | @Test fun `uint property does not emit hashCode method invocation`() { 20 | val classfile = jvmOutput("performance/UIntAndLong.class") 21 | val bytecode = bytecodeToText(classfile.readBytes()) 22 | assertThat(bytecode).all { 23 | contains("java/lang/Long.hashCode") 24 | doesNotContain("kotlin/UInt.hashCode-impl") 25 | } 26 | } 27 | 28 | @Test fun `toString uses invokedynamic on modern JDKs`() { 29 | val classfile = jvmOutput("performance/IntAndLong.class") 30 | val classReader = ClassReader(classfile.readBytes()) 31 | // Java 9 == class file major version 53: 32 | classReader.assumeMinimumClassVersion(53) 33 | val bytecode = classReader.toText() 34 | assertThat(bytecode).all { 35 | contains("INVOKEDYNAMIC makeConcatWithConstants") 36 | doesNotContain("StringBuilder") 37 | } 38 | } 39 | 40 | private fun ClassReader.assumeMinimumClassVersion(version: Int) { 41 | // Class file major version is a two-byte integer at offset 6: 42 | val actualClassVersion = readShort(6) 43 | if (actualClassVersion < version) { 44 | throw AssumptionViolatedException("This test only works class version $version+") 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /poko-tests/performance/src/test/kotlin/asm.kt: -------------------------------------------------------------------------------- 1 | import java.io.PrintWriter 2 | import java.io.StringWriter 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.util.Textifier 5 | import org.objectweb.asm.util.TraceClassVisitor 6 | 7 | fun bytecodeToText(bytecode: ByteArray): String { 8 | return ClassReader(bytecode).toText() 9 | } 10 | 11 | fun ClassReader.toText(): String { 12 | val textifier = Textifier() 13 | accept(TraceClassVisitor(null, textifier, null), 0) 14 | 15 | val writer = StringWriter() 16 | textifier.print(PrintWriter(writer)) 17 | return writer.toString().trim() 18 | } 19 | -------------------------------------------------------------------------------- /poko-tests/performance/src/test/kotlin/sources.kt: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | fun jvmOutput(relativePath: String) = File("../build/classes/kotlin/jvm/main", relativePath) 4 | fun jsOutput() = File("../build/compileSync/js/main/productionExecutable/kotlin/Poko-poko-tests.js") 5 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/Super.kt: -------------------------------------------------------------------------------- 1 | abstract class Super { 2 | override fun equals(other: Any?): Boolean = other == true 3 | override fun hashCode(): Int = 50934 4 | override fun toString(): String = "superclass" 5 | } 6 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/data/AnyArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | /** 4 | * Data classes don't implement array content checks, so [equals], [hashCode], and [toString] must 5 | * be written by hand. 6 | */ 7 | @Suppress("Unused") 8 | data class AnyArrayHolder( 9 | val any: Any, 10 | val nullableAny: Any?, 11 | val trailingProperty: String, 12 | ) { 13 | override fun equals(other: Any?): Boolean { 14 | if (this === other) 15 | return true 16 | 17 | if (other !is AnyArrayHolder) 18 | return false 19 | 20 | if (!this.any.arrayContentDeepEquals(other.any)) 21 | return false 22 | 23 | if (!this.nullableAny.arrayContentDeepEquals(other.nullableAny)) 24 | return false 25 | 26 | if (this.trailingProperty != other.trailingProperty) 27 | return false 28 | 29 | return true 30 | } 31 | 32 | @Suppress("NOTHING_TO_INLINE") 33 | private inline fun Any?.arrayContentDeepEquals(other: Any?): Boolean { 34 | return when (this) { 35 | is Array<*> -> other is Array<*> && this.contentDeepEquals(other) 36 | is BooleanArray -> other is BooleanArray && this.contentEquals(other) 37 | is ByteArray -> other is ByteArray && this.contentEquals(other) 38 | is CharArray -> other is CharArray && this.contentEquals(other) 39 | is ShortArray -> other is ShortArray && this.contentEquals(other) 40 | is IntArray -> other is IntArray && this.contentEquals(other) 41 | is LongArray -> other is LongArray && this.contentEquals(other) 42 | is FloatArray -> other is FloatArray && this.contentEquals(other) 43 | is DoubleArray -> other is DoubleArray && this.contentEquals(other) 44 | else -> this == other 45 | } 46 | } 47 | 48 | override fun hashCode(): Int { 49 | var result = any.arrayContentDeepHashCode() 50 | 51 | result = result * 31 + nullableAny.arrayContentDeepHashCode() 52 | result = result * 31 + trailingProperty.hashCode() 53 | 54 | return result 55 | } 56 | 57 | @Suppress("NOTHING_TO_INLINE") 58 | private inline fun Any?.arrayContentDeepHashCode(): Int { 59 | return when (this) { 60 | is Array<*> -> this.contentDeepHashCode() 61 | is BooleanArray -> this.contentHashCode() 62 | is ByteArray -> this.contentHashCode() 63 | is CharArray -> this.contentHashCode() 64 | is ShortArray -> this.contentHashCode() 65 | is IntArray -> this.contentHashCode() 66 | is LongArray -> this.contentHashCode() 67 | is FloatArray -> this.contentHashCode() 68 | is DoubleArray -> this.contentHashCode() 69 | else -> this.hashCode() 70 | } 71 | } 72 | 73 | override fun toString(): String { 74 | return StringBuilder() 75 | .append("AnyArrayHolder(") 76 | .append("any=") 77 | .append(any.arrayContentDeepToString()) 78 | .append(", ") 79 | .append("nullableAny=") 80 | .append(nullableAny.arrayContentDeepToString()) 81 | .append(", ") 82 | .append("trailingProperty=") 83 | .append(trailingProperty) 84 | .append(")") 85 | .toString() 86 | } 87 | 88 | @Suppress("NOTHING_TO_INLINE") 89 | private inline fun Any?.arrayContentDeepToString(): String { 90 | return when (this) { 91 | is Array<*> -> this.contentDeepToString() 92 | is BooleanArray -> this.contentToString() 93 | is ByteArray -> this.contentToString() 94 | is CharArray -> this.contentToString() 95 | is ShortArray -> this.contentToString() 96 | is IntArray -> this.contentToString() 97 | is LongArray -> this.contentToString() 98 | is FloatArray -> this.contentToString() 99 | is DoubleArray -> this.contentToString() 100 | else -> this.toString() 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/data/Complex.kt: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | /** 4 | * A data class version of [poko.Complex], useful for comparing generated [toString], [equals], and [hashCode]. 5 | */ 6 | @Suppress("Unused", "ArrayInDataClass") 7 | data class Complex( 8 | val referenceType: String, 9 | val nullableReferenceType: String?, 10 | val boolean: Boolean, 11 | val nullableBoolean: Boolean?, 12 | val int: Int, 13 | val nullableInt: Int?, 14 | val long: Long, 15 | val float: Float, 16 | val double: Double, 17 | val arrayReferenceType: Array, 18 | val nullableArrayReferenceType: Array?, 19 | val arrayPrimitiveType: IntArray, 20 | val nullableArrayPrimitiveType: IntArray?, 21 | val genericCollectionType: List, 22 | val nullableGenericCollectionType: List?, 23 | val genericType: T, 24 | val nullableGenericType: T? 25 | ) 26 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/data/ExplicitDeclarations.kt: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | data class ExplicitDeclarations( 4 | private val string: String 5 | ) { 6 | override fun toString() = string 7 | override fun equals(other: Any?) = other is ExplicitDeclarations && other.string.length == string.length 8 | override fun hashCode() = string.length 9 | } 10 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/data/Nested.kt: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | class OuterClass { 4 | data class Nested( 5 | val value: String 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/data/Simple.kt: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | /** 4 | * A data class version of [poko.Simple], useful for comparing generated [toString], [equals], and [hashCode]. 5 | */ 6 | @Suppress("unused") 7 | data class Simple( 8 | val int: Int, 9 | val requiredString: String, 10 | val optionalString: String? 11 | ) 12 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/data/SuperclassDeclarations.kt: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import Super 4 | 5 | data class SuperclassDeclarations( 6 | val number: Number 7 | ) : Super() 8 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/performance/IntAndLong.kt: -------------------------------------------------------------------------------- 1 | package performance 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko class IntAndLong( 7 | val int: Int, 8 | val long: Long, 9 | ) 10 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/performance/Main.kt: -------------------------------------------------------------------------------- 1 | package performance 2 | 3 | /** 4 | * An entrypoint for Kotlin/Native and Kotlin/JS. 5 | * Should use all types on which you want to assert. 6 | */ 7 | fun main() { 8 | println(IntAndLong(1, 2L) == IntAndLong(3, 4L)) 9 | } 10 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/performance/UIntAndLong.kt: -------------------------------------------------------------------------------- 1 | package performance 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko class UIntAndLong( 7 | val uint: UInt, 8 | val long: Long, 9 | ) 10 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/AnyArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class AnyArrayHolder( 7 | @Poko.ReadArrayContent val any: Any, 8 | @Poko.ReadArrayContent val nullableAny: Any?, 9 | val trailingProperty: String, 10 | ) 11 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/ArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class ArrayHolder( 7 | @Poko.ReadArrayContent val stringArray: Array, 8 | @Poko.ReadArrayContent val nullableStringArray: Array?, 9 | @Poko.ReadArrayContent val booleanArray: BooleanArray, 10 | @Poko.ReadArrayContent val nullableBooleanArray: BooleanArray?, 11 | @Poko.ReadArrayContent val byteArray: ByteArray, 12 | @Poko.ReadArrayContent val nullableByteArray: ByteArray?, 13 | @Poko.ReadArrayContent val charArray: CharArray, 14 | @Poko.ReadArrayContent val nullableCharArray: CharArray?, 15 | @Poko.ReadArrayContent val shortArray: ShortArray, 16 | @Poko.ReadArrayContent val nullableShortArray: ShortArray?, 17 | @Poko.ReadArrayContent val intArray: IntArray, 18 | @Poko.ReadArrayContent val nullableIntArray: IntArray?, 19 | @Poko.ReadArrayContent val longArray: LongArray, 20 | @Poko.ReadArrayContent val nullableLongArray: LongArray?, 21 | @Poko.ReadArrayContent val floatArray: FloatArray, 22 | @Poko.ReadArrayContent val nullableFloatArray: FloatArray?, 23 | @Poko.ReadArrayContent val doubleArray: DoubleArray, 24 | @Poko.ReadArrayContent val nullableDoubleArray: DoubleArray?, 25 | @Poko.ReadArrayContent val nestedStringArray: Array>, 26 | @Poko.ReadArrayContent val nestedIntArray: Array, 27 | ) 28 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/Complex.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class Complex( 7 | val referenceType: String, 8 | val nullableReferenceType: String?, 9 | val boolean: Boolean, 10 | val nullableBoolean: Boolean?, 11 | val int: Int, 12 | val nullableInt: Int?, 13 | val long: Long, 14 | val float: Float, 15 | val double: Double, 16 | val arrayReferenceType: Array, 17 | val nullableArrayReferenceType: Array?, 18 | val arrayPrimitiveType: IntArray, 19 | val nullableArrayPrimitiveType: IntArray?, 20 | val genericCollectionType: List, 21 | val nullableGenericCollectionType: List?, 22 | val genericType: T, 23 | val nullableGenericType: T? 24 | ) 25 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/ComplexGenericArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class ComplexGenericArrayHolder( 7 | @Poko.ReadArrayContent val generic: G, 8 | ) 9 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/Expected.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | expect class Expected( 4 | value: Int 5 | ) { 6 | val value: Int 7 | } 8 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/ExplicitDeclarations.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko class ExplicitDeclarations( 6 | private val string: String 7 | ) { 8 | override fun toString() = string 9 | override fun equals(other: Any?) = other is ExplicitDeclarations && other.string.length == string.length 10 | override fun hashCode() = string.length 11 | } 12 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/GenericArrayHolder.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class GenericArrayHolder( 7 | @Poko.ReadArrayContent val generic: G, 8 | ) 9 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/Nested.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | class OuterClass { 6 | @Poko class Nested( 7 | val value: String 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/Simple.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class Simple( 7 | val int: Int, 8 | val requiredString: String, 9 | val optionalString: String? 10 | ) 11 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/SimpleWithExtraParam.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("Unused") 6 | @Poko class SimpleWithExtraParam( 7 | val int: Int, 8 | val requiredString: String, 9 | val optionalString: String?, 10 | callback: (Unit) -> Boolean, 11 | ) { 12 | @Suppress("CanBePrimaryConstructorProperty") 13 | val callback: (Unit) -> Boolean = callback 14 | } 15 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/SkippedProperty.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | import dev.drewhamilton.poko.SkipSupport 5 | 6 | @OptIn(SkipSupport::class) 7 | @Suppress("Unused") 8 | @Poko class SkippedProperty( 9 | val id: String, 10 | @Poko.Skip val callback: () -> Unit, 11 | ) 12 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/SuperclassDeclarations.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import Super 4 | import dev.drewhamilton.poko.Poko 5 | 6 | @Poko class SuperclassDeclarations( 7 | val number: Number 8 | ) : Super() 9 | -------------------------------------------------------------------------------- /poko-tests/src/commonMain/kotlin/poko/SuperclassWithFinalOverrides.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | open class SuperclassWithFinalOverrides( 6 | private val id: String, 7 | ) { 8 | final override fun equals(other: Any?): Boolean = when { 9 | this === other -> true 10 | other !is SuperclassWithFinalOverrides -> false 11 | else -> this.id == other.id 12 | } 13 | 14 | final override fun hashCode(): Int = 31 + id.hashCode() 15 | 16 | final override fun toString(): String = id 17 | 18 | @Poko class Subclass( 19 | val name: String, 20 | ) : SuperclassWithFinalOverrides(id = "Subclass") 21 | } 22 | 23 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/ComplexGenericArrayHolderTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.assertThat 2 | import assertk.assertions.hashCodeFun 3 | import assertk.assertions.isEqualTo 4 | import assertk.assertions.toStringFun 5 | import kotlin.test.Test 6 | import poko.ComplexGenericArrayHolder 7 | 8 | class ComplexGenericArrayHolderTest { 9 | @Test fun two_ComplexGenericArrayHolder_instances_with_equivalent_int_arrays_are_equals() { 10 | val a = ComplexGenericArrayHolder( 11 | generic = intArrayOf(50, 100), 12 | ) 13 | val b = ComplexGenericArrayHolder( 14 | generic = intArrayOf(50, 100), 15 | ) 16 | assertThat(a).isEqualTo(b) 17 | assertThat(b).isEqualTo(a) 18 | } 19 | 20 | @Test fun hashCode_produces_expected_value() { 21 | val value = ComplexGenericArrayHolder( 22 | generic = intArrayOf(50, 100), 23 | ) 24 | // Ensure consistency across platforms: 25 | assertThat(value).hashCodeFun().isEqualTo(2611) 26 | } 27 | 28 | @Test fun toString_produces_expected_value() { 29 | val value = ComplexGenericArrayHolder( 30 | generic = intArrayOf(50, 100), 31 | ) 32 | assertThat(value).toStringFun().isEqualTo("ComplexGenericArrayHolder(generic=[50, 100])") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/ComplexTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertAll 3 | import assertk.assertThat 4 | import assertk.assertions.hashCodeFun 5 | import assertk.assertions.isEqualTo 6 | import assertk.assertions.isNotEqualTo 7 | import assertk.assertions.toStringFun 8 | import kotlin.test.Test 9 | import data.Complex as ComplexData 10 | import poko.Complex as ComplexPoko 11 | 12 | class ComplexTest { 13 | @Test fun two_equivalent_compiled_Complex_instances_match() { 14 | val arrayReferenceType = arrayOf("one string", "another string") 15 | val arrayPrimitiveType = intArrayOf(3, 4, 5) 16 | val a = ComplexPoko( 17 | referenceType = "Text", 18 | nullableReferenceType = null, 19 | boolean = true, 20 | nullableBoolean = null, 21 | int = 2, 22 | nullableInt = null, 23 | long = 12345L, 24 | float = 67f, 25 | double = 89.0, 26 | arrayReferenceType = arrayReferenceType, 27 | nullableArrayReferenceType = null, 28 | arrayPrimitiveType = arrayPrimitiveType, 29 | nullableArrayPrimitiveType = null, 30 | genericCollectionType = listOf(4, 6, 8).map { EvenInt(it) }, 31 | nullableGenericCollectionType = null, 32 | genericType = EvenInt(2), 33 | nullableGenericType = null, 34 | ) 35 | val b = ComplexPoko( 36 | referenceType = "Text", 37 | nullableReferenceType = null, 38 | boolean = true, 39 | nullableBoolean = null, 40 | int = 2, 41 | nullableInt = null, 42 | long = 12345L, 43 | float = 67f, 44 | double = 89.0, 45 | arrayReferenceType = arrayReferenceType, 46 | nullableArrayReferenceType = null, 47 | arrayPrimitiveType = arrayPrimitiveType, 48 | nullableArrayPrimitiveType = null, 49 | genericCollectionType = listOf(4, 6, 8).map { EvenInt(it) }, 50 | nullableGenericCollectionType = null, 51 | genericType = EvenInt(2), 52 | nullableGenericType = null, 53 | ) 54 | assertAll { 55 | assertThat(a).isEqualTo(b) 56 | assertThat(b).isEqualTo(a) 57 | 58 | assertThat(a).hashCodeFun().isEqualTo(b.hashCode()) 59 | assertThat(a).toStringFun().isEqualTo(b.toString()) 60 | } 61 | } 62 | 63 | @Test fun two_inequivalent_compiled_Complex_instances_are_not_equals() { 64 | val arrayReferenceType = arrayOf("one string", "another string") 65 | val arrayPrimitiveType = intArrayOf(3, 4, 5) 66 | val a = ComplexPoko( 67 | referenceType = "Text", 68 | nullableReferenceType = null, 69 | boolean = true, 70 | nullableBoolean = null, 71 | int = 2, 72 | nullableInt = null, 73 | long = 12345L, 74 | float = 67f, 75 | double = 89.0, 76 | arrayReferenceType = arrayReferenceType, 77 | nullableArrayReferenceType = null, 78 | arrayPrimitiveType = arrayPrimitiveType, 79 | nullableArrayPrimitiveType = null, 80 | genericCollectionType = listOf(4, 6, 8).map { EvenInt(it) }, 81 | nullableGenericCollectionType = null, 82 | genericType = EvenInt(2), 83 | nullableGenericType = null, 84 | ) 85 | val b = ComplexPoko( 86 | referenceType = "Text", 87 | nullableReferenceType = "non-null", 88 | boolean = true, 89 | nullableBoolean = null, 90 | int = 2, 91 | nullableInt = null, 92 | long = 12345L, 93 | float = 67f, 94 | double = 89.0, 95 | arrayReferenceType = arrayReferenceType, 96 | nullableArrayReferenceType = null, 97 | arrayPrimitiveType = arrayPrimitiveType, 98 | nullableArrayPrimitiveType = null, 99 | genericCollectionType = listOf(4, 6, 8).map { EvenInt(it) }, 100 | nullableGenericCollectionType = null, 101 | genericType = EvenInt(2), 102 | nullableGenericType = null, 103 | ) 104 | assertThat(a).isNotEqualTo(b) 105 | assertThat(b).isNotEqualTo(a) 106 | } 107 | 108 | @Test fun compiled_Complex_class_instance_has_expected_hashCode_and_toString() { 109 | val arrayReferenceType = arrayOf("one string", "another string") 110 | val arrayPrimitiveType = intArrayOf(3, 4, 5) 111 | val poko = ComplexPoko( 112 | referenceType = "Text", 113 | nullableReferenceType = null, 114 | boolean = true, 115 | nullableBoolean = null, 116 | int = 2, 117 | nullableInt = null, 118 | long = 12345L, 119 | float = 67f, 120 | double = 89.0, 121 | arrayReferenceType = arrayReferenceType, 122 | nullableArrayReferenceType = null, 123 | arrayPrimitiveType = arrayPrimitiveType, 124 | nullableArrayPrimitiveType = null, 125 | genericCollectionType = listOf(4, 6, 8).map { EvenInt(it) }, 126 | nullableGenericCollectionType = null, 127 | genericType = EvenInt(2), 128 | nullableGenericType = null, 129 | ) 130 | val data = ComplexData( 131 | referenceType = "Text", 132 | nullableReferenceType = null, 133 | boolean = true, 134 | nullableBoolean = null, 135 | int = 2, 136 | nullableInt = null, 137 | long = 12345L, 138 | float = 67f, 139 | double = 89.0, 140 | arrayReferenceType = arrayReferenceType, 141 | nullableArrayReferenceType = null, 142 | arrayPrimitiveType = arrayPrimitiveType, 143 | nullableArrayPrimitiveType = null, 144 | genericCollectionType = listOf(4, 6, 8).map { EvenInt(it) }, 145 | nullableGenericCollectionType = null, 146 | genericType = EvenInt(2), 147 | nullableGenericType = null, 148 | ) 149 | assertThat(poko).all { 150 | hashCodeFun().isEqualTo(data.hashCode()) 151 | toStringFun().isEqualTo(data.toString()) 152 | } 153 | } 154 | 155 | data class EvenInt(private val value: Int) : Number() { 156 | init { check(value % 2 == 0) } 157 | override fun toByte() = value.toByte() 158 | override fun toDouble() = value.toDouble() 159 | override fun toFloat() = value.toFloat() 160 | override fun toInt() = value 161 | override fun toLong() = value.toLong() 162 | override fun toShort() = value.toShort() 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/ExpectedTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import assertk.assertThat 3 | import assertk.assertions.isEqualTo 4 | import assertk.assertions.isNotEqualTo 5 | import kotlin.test.Test 6 | import poko.Expected 7 | 8 | class ExpectedTest { 9 | @Test fun two_equivalent_compiled_Expected_instances_are_equals() { 10 | val a = Expected(1) 11 | val b = Expected(1) 12 | assertThat(a).isEqualTo(b) 13 | assertThat(b).isEqualTo(a) 14 | assertThat(a.hashCode()).isEqualTo(b.hashCode()) 15 | } 16 | 17 | @Test fun two_inequivalent_compiled_Expected_instances_are_not_equals() { 18 | val a = Expected(2) 19 | val b = Expected(3) 20 | assertThat(a).isNotEqualTo(b) 21 | assertThat(b).isNotEqualTo(a) 22 | assertThat(a.hashCode()).isNotEqualTo(b.hashCode()) 23 | } 24 | 25 | @Test fun compiled_Expected_class_instance_has_expected_toString() { 26 | val actual = Expected(4) 27 | assertThat(actual.toString()).isEqualTo("Expected(value=4)") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/ExplicitDeclarationsTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertThat 3 | import assertk.assertions.hashCodeFun 4 | import assertk.assertions.isEqualTo 5 | import assertk.assertions.isNotEqualTo 6 | import assertk.assertions.toStringFun 7 | import kotlin.test.Test 8 | import data.ExplicitDeclarations as ExplicitDeclarationsData 9 | import poko.ExplicitDeclarations as ExplicitDeclarationsPoko 10 | 11 | class ExplicitDeclarationsTest { 12 | @Test fun two_equivalent_compiled_ExplicitDeclarations_instances_are_equals() { 13 | val a = ExplicitDeclarationsPoko("string 1") 14 | val b = ExplicitDeclarationsPoko("string 2") 15 | assertThat(a).isEqualTo(b) 16 | assertThat(b).isEqualTo(a) 17 | } 18 | 19 | @Test fun two_inequivalent_compiled_ExplicitDeclarations_instances_are_not_equals() { 20 | val a = ExplicitDeclarationsPoko("string 1") 21 | val b = ExplicitDeclarationsPoko("string 11") 22 | assertThat(a).isNotEqualTo(b) 23 | assertThat(b).isNotEqualTo(a) 24 | } 25 | 26 | @Test fun compilation_with_explicit_function_declarations_respects_explicit_hashCode() { 27 | val testString = "test string" 28 | val poko = ExplicitDeclarationsPoko(testString) 29 | val data = ExplicitDeclarationsData(testString) 30 | 31 | assertThat(poko).all { 32 | hashCodeFun().all { 33 | isEqualTo(testString.length) 34 | isEqualTo(data.hashCode()) 35 | } 36 | toStringFun().all { 37 | isEqualTo(testString) 38 | isEqualTo(data.toString()) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/GenericArrayHolderTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import assertk.assertThat 3 | import assertk.assertions.hashCodeFun 4 | import assertk.assertions.isEqualTo 5 | import assertk.assertions.isNotEqualTo 6 | import assertk.assertions.toStringFun 7 | import kotlin.test.Test 8 | import poko.GenericArrayHolder 9 | 10 | class GenericArrayHolderTest { 11 | @Test fun two_GenericArrayHolder_instances_with_equivalent_typed_arrays_are_equals() { 12 | val a = GenericArrayHolder( 13 | generic = arrayOf( 14 | arrayOf("5%, 10%"), 15 | intArrayOf(5, 10), 16 | booleanArrayOf(false, true), 17 | Unit, 18 | ), 19 | ) 20 | val b = GenericArrayHolder( 21 | generic = arrayOf( 22 | arrayOf("5%, 10%"), 23 | intArrayOf(5, 10), 24 | booleanArrayOf(false, true), 25 | Unit, 26 | ), 27 | ) 28 | 29 | assertThat(a).isEqualTo(b) 30 | assertThat(b).isEqualTo(a) 31 | assertThat(a).hashCodeFun().isEqualTo(b.hashCode()) 32 | assertThat(a).toStringFun().isEqualTo(b.toString()) 33 | } 34 | 35 | @Test fun two_GenericArrayHolder_instances_with_equivalent_int_arrays_are_equals() { 36 | val a = GenericArrayHolder(intArrayOf(5, 10)) 37 | val b = GenericArrayHolder(intArrayOf(5, 10)) 38 | assertThat(a).isEqualTo(b) 39 | assertThat(b).isEqualTo(a) 40 | assertThat(a).hashCodeFun().isEqualTo(b.hashCode()) 41 | assertThat(a).toStringFun().isEqualTo(b.toString()) 42 | } 43 | 44 | @Test fun two_GenericArrayHolder_instances_with_equivalent_nonarrays_are_equals() { 45 | val a = GenericArrayHolder("5, 10") 46 | val b = GenericArrayHolder("5, 10") 47 | assertThat(a).isEqualTo(b) 48 | assertThat(b).isEqualTo(a) 49 | assertThat(a).hashCodeFun().isEqualTo(b.hashCode()) 50 | assertThat(a).toStringFun().isEqualTo(b.toString()) 51 | } 52 | 53 | @Test fun two_GenericArrayHolder_instances_holding_inequivalent_long_arrays_are_not_equals() { 54 | val a = GenericArrayHolder(longArrayOf(Long.MIN_VALUE)) 55 | val b = GenericArrayHolder(longArrayOf(Long.MAX_VALUE)) 56 | assertThat(a).isNotEqualTo(b) 57 | assertThat(b).isNotEqualTo(a) 58 | } 59 | 60 | @Test fun two_GenericArrayHolder_instances_holding_mismatching_types_are_not_equals() { 61 | val a = GenericArrayHolder(arrayOf("x", "y")) 62 | val b = GenericArrayHolder("xy") 63 | assertThat(a).isNotEqualTo(b) 64 | assertThat(b).isNotEqualTo(a) 65 | } 66 | 67 | @Test fun hashCode_produces_expected_value() { 68 | val value = GenericArrayHolder( 69 | generic = intArrayOf(50, 100), 70 | ) 71 | // Ensure consistency across platforms: 72 | assertThat(value).hashCodeFun().isEqualTo(2611) 73 | } 74 | 75 | @Test fun toString_produces_expected_value() { 76 | val value = GenericArrayHolder( 77 | generic = intArrayOf(50, 100), 78 | ) 79 | assertThat(value).toStringFun().isEqualTo("GenericArrayHolder(generic=[50, 100])") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/NestedTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertThat 3 | import assertk.assertions.hashCodeFun 4 | import assertk.assertions.isEqualTo 5 | import assertk.assertions.isNotEqualTo 6 | import assertk.assertions.toStringFun 7 | import kotlin.test.Test 8 | import data.OuterClass.Nested as NestedData 9 | import poko.OuterClass.Nested as NestedPoko 10 | 11 | class NestedTest { 12 | @Test fun two_equivalent_compiled_Nested_instances_are_equals() { 13 | val a = NestedPoko("string 1") 14 | val b = NestedPoko("string 1") 15 | assertThat(a).isEqualTo(b) 16 | assertThat(b).isEqualTo(a) 17 | } 18 | 19 | @Test fun two_inequivalent_compiled_Nested_instances_are_not_equals() { 20 | val a = NestedPoko("string 1") 21 | val b = NestedPoko("string 2") 22 | assertThat(a).isNotEqualTo(b) 23 | assertThat(b).isNotEqualTo(a) 24 | } 25 | 26 | @Test fun compilation_of_nested_class_within_class_matches_corresponding_data_class_hashCode() { 27 | val poko = NestedPoko("nested class value") 28 | val data = NestedData("nested class value") 29 | assertThat(poko).all { 30 | hashCodeFun().isEqualTo(data.hashCode()) 31 | toStringFun().isEqualTo(data.toString()) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/SimpleTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertThat 3 | import assertk.assertions.hashCodeFun 4 | import assertk.assertions.isEqualTo 5 | import assertk.assertions.isNotEqualTo 6 | import assertk.assertions.toStringFun 7 | import kotlin.test.Test 8 | import data.Simple as SimpleData 9 | import poko.Simple as SimplePoko 10 | 11 | class SimpleTest { 12 | @Test fun two_equivalent_compiled_Simple_instances_are_equals() { 13 | val a = SimplePoko(1, "String", null) 14 | val b = SimplePoko(1, "String", null) 15 | assertThat(a).isEqualTo(b) 16 | assertThat(b).isEqualTo(a) 17 | } 18 | 19 | @Test fun two_inequivalent_compiled_Simple_instances_are_not_equals() { 20 | val a = SimplePoko(1, "String", null) 21 | val b = SimplePoko(1, "String", "non-null") 22 | assertThat(a).isNotEqualTo(b) 23 | assertThat(b).isNotEqualTo(a) 24 | } 25 | 26 | @Test fun compiled_Simple_class_instance_has_expected_hashCode() { 27 | val poko = SimplePoko(1, "String", null) 28 | val data = SimpleData(1, "String", null) 29 | assertThat(poko).all { 30 | hashCodeFun().isEqualTo(data.hashCode()) 31 | toStringFun().isEqualTo(data.toString()) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/SimpleWithExtraParamTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.assertThat 2 | import assertk.assertions.hashCodeFun 3 | import assertk.assertions.isEqualTo 4 | import assertk.assertions.toStringFun 5 | import kotlin.test.Test 6 | import poko.SimpleWithExtraParam 7 | 8 | class SimpleWithExtraParamTest { 9 | @Test fun nonproperty_parameter_is_ignored_for_equals() { 10 | val a = SimpleWithExtraParam(1, "String", null, { true }) 11 | val b = SimpleWithExtraParam(1, "String", null, { false }) 12 | assertThat(a).isEqualTo(b) 13 | assertThat(b).isEqualTo(a) 14 | assertThat(a).hashCodeFun().isEqualTo(b.hashCode()) 15 | assertThat(a).toStringFun().isEqualTo(b.toString()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/SkippedPropertyTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import assertk.assertAll 3 | import assertk.assertThat 4 | import assertk.assertions.isEqualTo 5 | import kotlin.test.Test 6 | import poko.SkippedProperty 7 | 8 | class SkippedPropertyTest { 9 | 10 | @Test fun skipped_property_omitted_from_all_generated_functions() { 11 | val a = SkippedProperty( 12 | id = "id", 13 | callback = { println("Callback invoked") }, 14 | ) 15 | val b = SkippedProperty( 16 | id = "id", 17 | callback = { println("Callback invoked") }, 18 | ) 19 | 20 | assertAll { 21 | assertThat(a).isEqualTo(b) 22 | assertThat(b).isEqualTo(a) 23 | assertThat(a.hashCode()).isEqualTo(b.hashCode()) 24 | assertThat(a.toString()).isEqualTo(b.toString()) 25 | assertThat(a.toString()).isEqualTo("SkippedProperty(id=id)") 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/SuperclassDeclarationsTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertThat 3 | import assertk.assertions.hashCodeFun 4 | import assertk.assertions.isEqualTo 5 | import assertk.assertions.isNotEqualTo 6 | import assertk.assertions.toStringFun 7 | import kotlin.test.Test 8 | import data.SuperclassDeclarations as SuperclassDeclarationsData 9 | import poko.SuperclassDeclarations as SuperclassDeclarationsPoko 10 | 11 | class SuperclassDeclarationsTest { 12 | @Test fun two_equivalent_compiled_Subclass_instances_are_equals() { 13 | val a = SuperclassDeclarationsPoko(999.9) 14 | val b = SuperclassDeclarationsPoko(999.9) 15 | 16 | // Super class equals implementation returns `other == true`; this confirms that is overridden: 17 | assertThat(a).isNotEqualTo(true) 18 | assertThat(b).isNotEqualTo(true) 19 | 20 | assertThat(a).isEqualTo(b) 21 | assertThat(b).isEqualTo(a) 22 | } 23 | 24 | @Test fun two_inequivalent_compiled_Subclass_instances_are_not_equals() { 25 | val a = SuperclassDeclarationsPoko(999.9) 26 | val b = SuperclassDeclarationsPoko(888.8) 27 | // Super class equals implementation returns `other == true`; this confirms that is overridden: 28 | assertThat(a).isNotEqualTo(true) 29 | assertThat(b).isNotEqualTo(true) 30 | 31 | assertThat(a).isNotEqualTo(b) 32 | assertThat(b).isNotEqualTo(a) 33 | } 34 | 35 | @Test fun superclass_hashCode_is_overridden() { 36 | val poko = SuperclassDeclarationsPoko(123.4) 37 | val data = SuperclassDeclarationsData(123.4) 38 | assertThat(poko).all { 39 | hashCodeFun().all { 40 | isEqualTo(data.hashCode()) 41 | isNotEqualTo(50934) 42 | } 43 | toStringFun().all { 44 | isEqualTo(data.toString()) 45 | isNotEqualTo("superclass") 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /poko-tests/src/commonTest/kotlin/SuperclassWithFinalOverridesTest.kt: -------------------------------------------------------------------------------- 1 | import assertk.all 2 | import assertk.assertThat 3 | import assertk.assertions.hashCodeFun 4 | import assertk.assertions.isEqualTo 5 | import assertk.assertions.toStringFun 6 | import kotlin.test.Test 7 | import poko.SuperclassWithFinalOverrides 8 | 9 | class SuperclassWithFinalOverridesTest { 10 | 11 | @Test fun successful_instantiation_with_final_function_overrides_in_superclass() { 12 | val instance = SuperclassWithFinalOverrides.Subclass(name = "this-is-fine") 13 | assertThat(instance).all { 14 | toStringFun().isEqualTo("Subclass") 15 | hashCodeFun().isEqualTo(31 + "Subclass".hashCode()) 16 | isEqualTo(SuperclassWithFinalOverrides.Subclass(name = "different-name")) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /poko-tests/src/jsMain/kotlin/poko/Expected.js.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko 6 | actual class Expected actual constructor( 7 | actual val value: Int, 8 | ) 9 | -------------------------------------------------------------------------------- /poko-tests/src/jvmMain/kotlin/poko/Expected.jvm.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko 6 | actual class Expected actual constructor( 7 | actual val value: Int, 8 | ) 9 | -------------------------------------------------------------------------------- /poko-tests/src/nativeMain/kotlin/poko/Expected.native.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko 6 | actual class Expected actual constructor( 7 | actual val value: Int, 8 | ) 9 | -------------------------------------------------------------------------------- /poko-tests/src/wasmJsMain/kotlin/poko/Expected.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko 6 | actual class Expected actual constructor( 7 | actual val value: Int, 8 | ) 9 | -------------------------------------------------------------------------------- /poko-tests/src/wasmWasiMain/kotlin/poko/Expected.wasmWasi.kt: -------------------------------------------------------------------------------- 1 | package poko 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Poko 6 | actual class Expected actual constructor( 7 | actual val value: Int, 8 | ) 9 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'config:recommended', 5 | ], 6 | configMigration: true, 7 | packageRules: [ 8 | { 9 | // Compiler tools are tightly coupled to Kotlin version: 10 | groupName: 'Kotlin', 11 | matchPackageNames: [ 12 | 'androidx.compose.compiler{/,}**', 13 | 'com.google.devtools.ksp{/,}**', 14 | 'com.github.tschuchortdev:kotlin-compile-testing{/,}**', 15 | 'dev.zacsweers.kctfork{/,}**', 16 | 'org.jetbrains.kotlin{/,}**', 17 | 'org.jetbrains.kotlinx:binary-compatibility-validator{/,}**', 18 | ], 19 | }, 20 | { 21 | groupName: 'Upload/download artifact', 22 | matchPackageNames: [ 23 | 'actions/download-artifact', 24 | 'actions/upload-artifact', 25 | ], 26 | }, 27 | ], 28 | ignoreDeps: [ 29 | // These should just match the main Kotlin version: 30 | 'org.jetbrains.kotlin:kotlin-compiler-embeddable', 31 | 'org.jetbrains.kotlin:kotlin-gradle-plugin-api', 32 | 'org.jetbrains.kotlin:kotlin-stdlib', 33 | 'org.jetbrains.kotlin:kotlin-test', 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.drewhamilton.poko.sample.build.jvmToolchainLanguageVersion 2 | import dev.drewhamilton.poko.sample.build.kotlinJvmTarget 3 | import dev.drewhamilton.poko.sample.build.resolvedJavaVersion 4 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension 5 | import org.jetbrains.kotlin.gradle.dsl.KotlinVersion 6 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 7 | 8 | plugins { 9 | val ciJavaVersion = dev.drewhamilton.poko.sample.build.ciJavaVersion 10 | if (ciJavaVersion == null || ciJavaVersion >= 17) { 11 | alias(libs.plugins.android.library) apply false 12 | } 13 | alias(libs.plugins.kotlin.android) apply false 14 | alias(libs.plugins.kotlin.jvm) apply false 15 | alias(libs.plugins.kotlin.multiplatform) apply false 16 | id("dev.drewhamilton.poko") apply false 17 | } 18 | apply(from = "properties.gradle") 19 | 20 | logger.lifecycle("Compiling sample app with Kotlin ${libs.versions.kotlin.get()}") 21 | logger.lifecycle("Targeting Java version $resolvedJavaVersion") 22 | 23 | val specifiedKotlinLanguageVersion = findProperty("pokoSample_kotlinLanguageVersion") 24 | ?.toString() 25 | ?.let { it.ifBlank { null } } 26 | ?.let { KotlinVersion.fromVersion(it) } 27 | ?.also { logger.lifecycle("Compiling sample project with language version $it") } 28 | 29 | allprojects { 30 | repositories { 31 | if (System.getenv()["CI"] == "true") { 32 | logger.lifecycle("Resolving ${this@allprojects} Poko dependencies from MavenLocal") 33 | exclusiveContent { 34 | forRepository { mavenLocal() } 35 | filter { 36 | includeGroup(property("PUBLISH_GROUP") as String) 37 | } 38 | } 39 | } 40 | mavenCentral() 41 | 42 | val kotlinDevRepository = rootProject.findProperty("kotlin_dev_repository") 43 | if (kotlinDevRepository != null) { 44 | logger.lifecycle("Adding <$kotlinDevRepository> repository for ${this@allprojects}") 45 | maven { url = uri(kotlinDevRepository) } 46 | } 47 | } 48 | 49 | listOf( 50 | "org.jetbrains.kotlin.jvm", 51 | "org.jetbrains.kotlin.multiplatform", 52 | ).forEach { id -> 53 | plugins.withId(id) { 54 | if (jvmToolchainLanguageVersion == null) { 55 | with(extensions.getByType()) { 56 | sourceCompatibility = resolvedJavaVersion 57 | targetCompatibility = resolvedJavaVersion 58 | } 59 | } else { 60 | extensions.getByType().jvmToolchain { 61 | languageVersion.set(jvmToolchainLanguageVersion) 62 | } 63 | } 64 | } 65 | } 66 | 67 | tasks.withType().configureEach { 68 | compilerOptions { 69 | jvmTarget = kotlinJvmTarget 70 | languageVersion = specifiedKotlinLanguageVersion 71 | progressiveMode = (specifiedKotlinLanguageVersion == null) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sample/buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation(libs.kotlin.gradleApi) 11 | } 12 | -------------------------------------------------------------------------------- /sample/buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | create("libs") { 4 | from(files("../../gradle/libs.versions.toml")) 5 | 6 | //region Duplicated in ../settings.gradle 7 | fun String.nullIfBlank(): String? = if (isNullOrBlank()) null else this 8 | 9 | // Compile sample project with different Kotlin version than Poko, if provided: 10 | val kotlinVersionOverride = System.getenv()["poko_sample_kotlin_version"]?.nullIfBlank() 11 | kotlinVersionOverride?.let { kotlinVersion -> 12 | version("kotlin", kotlinVersion) 13 | } 14 | //endregion 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/buildSrc/src/main/kotlin/dev/drewhamilton/poko/sample/build/java.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.build 2 | 3 | import org.gradle.api.JavaVersion 4 | import org.gradle.jvm.toolchain.JavaLanguageVersion 5 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 6 | 7 | val ciJavaVersion: Int? = System.getenv()["ci_java_version"]?.toInt() 8 | 9 | val jvmToolchainLanguageVersion: JavaLanguageVersion? = ciJavaVersion?.let { 10 | JavaLanguageVersion.of(ciJavaVersion.toInt()) 11 | } 12 | 13 | val resolvedJavaVersion: JavaVersion = when (ciJavaVersion) { 14 | null -> JavaVersion.VERSION_11 15 | 8, 9, 10 -> JavaVersion.valueOf("VERSION_1_$ciJavaVersion") 16 | else -> JavaVersion.valueOf("VERSION_$ciJavaVersion") 17 | } 18 | 19 | val kotlinJvmTarget: JvmTarget = when (resolvedJavaVersion) { 20 | JavaVersion.VERSION_1_8 -> JvmTarget.JVM_1_8 21 | else -> JvmTarget.valueOf("JVM_${resolvedJavaVersion.majorVersion}") 22 | } 23 | -------------------------------------------------------------------------------- /sample/compose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.drewhamilton.poko.sample.build.jvmToolchainLanguageVersion 2 | import dev.drewhamilton.poko.sample.build.resolvedJavaVersion 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.kotlin.android) 7 | alias(libs.plugins.kotlin.compose) 8 | id("dev.drewhamilton.poko") 9 | } 10 | 11 | poko { 12 | pokoAnnotation.set("dev/drewhamilton/poko/sample/compose/Poko") 13 | } 14 | 15 | if (jvmToolchainLanguageVersion != null) { 16 | kotlin { 17 | jvmToolchain { 18 | languageVersion.set(jvmToolchainLanguageVersion) 19 | } 20 | } 21 | } 22 | 23 | android { 24 | namespace = "dev.drewhamilton.poko.sample.compose" 25 | compileSdk = 34 26 | 27 | defaultConfig { 28 | minSdk = 21 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility(resolvedJavaVersion) 33 | targetCompatibility(resolvedJavaVersion) 34 | } 35 | 36 | kotlinOptions { 37 | freeCompilerArgs = listOf("-progressive") 38 | } 39 | 40 | buildFeatures { 41 | compose = true 42 | 43 | // Disable unused AGP features 44 | resValues = false 45 | shaders = false 46 | androidResources = false 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation(libs.androidx.compose.runtime) 52 | 53 | testImplementation(libs.junit) 54 | testImplementation(libs.assertk) 55 | } 56 | 57 | repositories { 58 | google() 59 | } 60 | -------------------------------------------------------------------------------- /sample/compose/src/main/kotlin/dev/drewhamilton/poko/sample/compose/Poko.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.compose 2 | 3 | /** 4 | * Annotation used for Poko compiler plugin, which generates [equals], [hashCode], and [toString] 5 | * for the annotated class. 6 | */ 7 | @Retention(AnnotationRetention.SOURCE) 8 | @Target(AnnotationTarget.CLASS) 9 | annotation class Poko 10 | -------------------------------------------------------------------------------- /sample/compose/src/main/kotlin/dev/drewhamilton/poko/sample/compose/Sample.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.compose 2 | 3 | @Suppress("unused") 4 | @Poko class Sample( 5 | val int: Int, 6 | val requiredString: String, 7 | val optionalString: String?, 8 | ) 9 | -------------------------------------------------------------------------------- /sample/compose/src/test/kotlin/dev/drewhamilton/poko/sample/compose/SampleTest.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.compose 2 | 3 | import assertk.assertThat 4 | import assertk.assertions.isEqualTo 5 | import org.junit.Test 6 | 7 | class SampleTest { 8 | 9 | @Test fun `equals works`() { 10 | val a = Sample( 11 | int = 1, 12 | requiredString = "String", 13 | optionalString = null 14 | ) 15 | val b = Sample( 16 | int = 1, 17 | requiredString = "String", 18 | optionalString = null 19 | ) 20 | 21 | assertThat(a).isEqualTo(b) 22 | assertThat(b).isEqualTo(a) 23 | } 24 | 25 | @Test fun `hashCode is consistent`() { 26 | val a = Sample( 27 | int = 1, 28 | requiredString = "String", 29 | optionalString = null 30 | ) 31 | val b = Sample( 32 | int = 1, 33 | requiredString = "String", 34 | optionalString = null 35 | ) 36 | 37 | assertThat(a.hashCode()).isEqualTo(b.hashCode()) 38 | } 39 | 40 | @Test fun `hashCode is equivalent to data class hashCode`() { 41 | val dataApi = Sample( 42 | int = 1, 43 | requiredString = "String", 44 | optionalString = null 45 | ) 46 | 47 | val data = DataSample( 48 | int = 1, 49 | requiredString = "String", 50 | optionalString = null 51 | ) 52 | 53 | assertThat(dataApi.hashCode()).isEqualTo(data.hashCode()) 54 | } 55 | 56 | @Test fun `toString includes class name and each property`() { 57 | val sample = Sample(3, "sample", null) 58 | assertThat(sample.toString()).isEqualTo("Sample(int=3, requiredString=sample, optionalString=null)") 59 | } 60 | 61 | @Test fun `toString is equivalent to data class toString`() { 62 | val dataApi = Sample( 63 | int = 99, 64 | requiredString = "test", 65 | optionalString = null 66 | ) 67 | 68 | val data = DataSample( 69 | int = 99, 70 | requiredString = "test", 71 | optionalString = null 72 | ) 73 | 74 | assertThat(dataApi.toString()).isEqualTo(data.toString().removePrefix("Data")) 75 | } 76 | 77 | /** 78 | * Data class equivalent to [Sample]. 79 | */ 80 | private data class DataSample( 81 | val int: Int, 82 | val requiredString: String, 83 | val optionalString: String? 84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /sample/gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drewhamilton/Poko/15214b7c01e3e080dcba61079fc9ffdaddb775d5/sample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /sample/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /sample/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /sample/jvm/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | id("dev.drewhamilton.poko") 4 | `java-test-fixtures` 5 | } 6 | 7 | poko { 8 | pokoAnnotation = "dev/drewhamilton/poko/sample/jvm/MyData" 9 | } 10 | 11 | dependencies { 12 | testImplementation(libs.junit) 13 | testImplementation(libs.assertk) 14 | } 15 | -------------------------------------------------------------------------------- /sample/jvm/src/main/kotlin/dev/drewhamilton/poko/sample/jvm/MyData.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.jvm 2 | 3 | /** 4 | * Local replacement for default Poko annotation. 5 | */ 6 | @Retention(AnnotationRetention.SOURCE) 7 | @Target(AnnotationTarget.CLASS) 8 | annotation class MyData 9 | -------------------------------------------------------------------------------- /sample/jvm/src/main/kotlin/dev/drewhamilton/poko/sample/jvm/Sample.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.jvm 2 | 3 | @Suppress("unused") 4 | @MyData class Sample( 5 | val int: Int, 6 | val requiredString: String, 7 | val optionalString: String?, 8 | ) 9 | -------------------------------------------------------------------------------- /sample/jvm/src/test/kotlin/dev/drewhamilton/poko/sample/jvm/SampleTest.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.jvm 2 | 3 | import assertk.assertThat 4 | import assertk.assertions.isEqualTo 5 | import org.junit.Test 6 | 7 | class SampleTest { 8 | 9 | @Test fun `equals works`() { 10 | val a = Sample( 11 | int = 1, 12 | requiredString = "String", 13 | optionalString = null 14 | ) 15 | val b = Sample( 16 | int = 1, 17 | requiredString = "String", 18 | optionalString = null 19 | ) 20 | 21 | assertThat(a).isEqualTo(b) 22 | assertThat(b).isEqualTo(a) 23 | } 24 | 25 | @Test fun `hashCode is consistent`() { 26 | val a = Sample( 27 | int = 1, 28 | requiredString = "String", 29 | optionalString = null 30 | ) 31 | val b = Sample( 32 | int = 1, 33 | requiredString = "String", 34 | optionalString = null 35 | ) 36 | 37 | assertThat(a.hashCode()).isEqualTo(b.hashCode()) 38 | } 39 | 40 | @Test fun `hashCode is equivalent to data class hashCode`() { 41 | val dataApi = Sample( 42 | int = 1, 43 | requiredString = "String", 44 | optionalString = null 45 | ) 46 | 47 | val data = DataSample( 48 | int = 1, 49 | requiredString = "String", 50 | optionalString = null 51 | ) 52 | 53 | assertThat(dataApi.hashCode()).isEqualTo(data.hashCode()) 54 | } 55 | 56 | @Test fun `toString includes class name and each property`() { 57 | val sample = Sample(3, "sample", null) 58 | assertThat(sample.toString()) 59 | .isEqualTo("Sample(int=3, requiredString=sample, optionalString=null)") 60 | } 61 | 62 | @Test fun `toString is equivalent to data class toString`() { 63 | val dataApi = Sample( 64 | int = 99, 65 | requiredString = "test", 66 | optionalString = null 67 | ) 68 | 69 | val data = DataSample( 70 | int = 99, 71 | requiredString = "test", 72 | optionalString = null 73 | ) 74 | 75 | assertThat(dataApi.toString()).isEqualTo(data.toString().removePrefix("Data")) 76 | } 77 | 78 | /** 79 | * Data class equivalent to [Sample]. 80 | */ 81 | private data class DataSample( 82 | val int: Int, 83 | val requiredString: String, 84 | val optionalString: String? 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /sample/jvm/src/testFixtures/kotlin/dev/drewhamilton/poko/sample/jvm/SampleFixture.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.jvm 2 | 3 | @Suppress("unused") // Used to validate compilation works as expected 4 | val SampleFixture = Sample( 5 | int = 1, 6 | requiredString = "String", 7 | optionalString = null 8 | ) 9 | -------------------------------------------------------------------------------- /sample/mpp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) 3 | id("dev.drewhamilton.poko") 4 | } 5 | 6 | kotlin { 7 | js().nodejs() 8 | 9 | jvm() 10 | 11 | // All native "desktop" platforms to ensure at least one set of native tests will run. 12 | linuxArm64() 13 | linuxX64() 14 | macosArm64() 15 | macosX64() 16 | mingwX64() 17 | 18 | sourceSets { 19 | commonTest { 20 | dependencies { 21 | implementation(libs.kotlin.test) 22 | implementation(libs.assertk) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/mpp/src/commonMain/kotlin/dev/drewhamilton/poko/sample/mpp/ArraysSample.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.mpp 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko class ArraysSample( 7 | @Poko.ReadArrayContent val primitive: ByteArray, 8 | @Poko.ReadArrayContent val standard: Array, 9 | @Poko.ReadArrayContent val nested: Array, 10 | @Poko.ReadArrayContent val runtime: Any, 11 | ) 12 | -------------------------------------------------------------------------------- /sample/mpp/src/commonMain/kotlin/dev/drewhamilton/poko/sample/mpp/Sample.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.mpp 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | @Suppress("unused") 6 | @Poko class Sample( 7 | val int: Int, 8 | val requiredString: String, 9 | val optionalString: String?, 10 | ) 11 | -------------------------------------------------------------------------------- /sample/mpp/src/commonTest/kotlin/dev/drewhamilton/poko/sample/mpp/ArraysSampleTest.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.mpp 2 | 3 | import assertk.assertThat 4 | import assertk.assertions.isEqualTo 5 | import kotlin.test.Test 6 | 7 | class ArraysSampleTest { 8 | 9 | @Test fun equals_works() { 10 | val a = ArraysSample( 11 | primitive = byteArrayOf(0x1F.toByte()), 12 | standard = arrayOf("string 1", "string 2"), 13 | nested = arrayOf(charArrayOf('a'), charArrayOf('b')), 14 | runtime = arrayOf("one", 2), 15 | ) 16 | val b = ArraysSample( 17 | primitive = byteArrayOf(0x1F.toByte()), 18 | standard = arrayOf("string 1", "string 2"), 19 | nested = arrayOf(charArrayOf('a'), charArrayOf('b')), 20 | runtime = arrayOf("one", 2), 21 | ) 22 | 23 | assertThat(a).isEqualTo(b) 24 | assertThat(b).isEqualTo(a) 25 | } 26 | 27 | @Test fun hashCode_is_consistent() { 28 | val a = ArraysSample( 29 | primitive = byteArrayOf(0x1F.toByte()), 30 | standard = arrayOf("string 1", "string 2"), 31 | nested = arrayOf(charArrayOf('a'), charArrayOf('b')), 32 | runtime = arrayOf("one", 2), 33 | ) 34 | val b = ArraysSample( 35 | primitive = byteArrayOf(0x1F.toByte()), 36 | standard = arrayOf("string 1", "string 2"), 37 | nested = arrayOf(charArrayOf('a'), charArrayOf('b')), 38 | runtime = arrayOf("one", 2), 39 | ) 40 | 41 | assertThat(a.hashCode()).isEqualTo(b.hashCode()) 42 | } 43 | 44 | @Test fun toString_includes_class_name_and_each_property() { 45 | val sample = ArraysSample( 46 | primitive = byteArrayOf(0x1F.toByte()), 47 | standard = arrayOf("string 1", "string 2"), 48 | nested = arrayOf(charArrayOf('a'), charArrayOf('b')), 49 | runtime = arrayOf("one", 2), 50 | ) 51 | assertThat(sample.toString()).isEqualTo( 52 | other = "ArraysSample(" + 53 | "primitive=[31], " + 54 | "standard=[string 1, string 2], " + 55 | "nested=[[a], [b]], " + 56 | "runtime=[one, 2])", 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sample/mpp/src/commonTest/kotlin/dev/drewhamilton/poko/sample/mpp/SampleTest.kt: -------------------------------------------------------------------------------- 1 | package dev.drewhamilton.poko.sample.mpp 2 | 3 | import assertk.assertThat 4 | import assertk.assertions.isEqualTo 5 | import kotlin.test.Test 6 | 7 | class SampleTest { 8 | 9 | @Test fun equals_works() { 10 | val a = Sample( 11 | int = 1, 12 | requiredString = "String", 13 | optionalString = null 14 | ) 15 | val b = Sample( 16 | int = 1, 17 | requiredString = "String", 18 | optionalString = null 19 | ) 20 | 21 | assertThat(a).isEqualTo(b) 22 | assertThat(b).isEqualTo(a) 23 | } 24 | 25 | @Test fun hashCode_is_consistent() { 26 | val a = Sample( 27 | int = 1, 28 | requiredString = "String", 29 | optionalString = null 30 | ) 31 | val b = Sample( 32 | int = 1, 33 | requiredString = "String", 34 | optionalString = null 35 | ) 36 | 37 | assertThat(a.hashCode()).isEqualTo(b.hashCode()) 38 | } 39 | 40 | @Test fun hashCode_is_equivalent_to_data_class_hashCode() { 41 | val dataApi = Sample( 42 | int = 1, 43 | requiredString = "String", 44 | optionalString = null 45 | ) 46 | 47 | val data = DataSample( 48 | int = 1, 49 | requiredString = "String", 50 | optionalString = null 51 | ) 52 | 53 | assertThat(dataApi.hashCode()).isEqualTo(data.hashCode()) 54 | } 55 | 56 | @Test fun toString_includes_class_name_and_each_property() { 57 | val sample = Sample(3, "sample", null) 58 | assertThat(sample.toString()) 59 | .isEqualTo("Sample(int=3, requiredString=sample, optionalString=null)") 60 | } 61 | 62 | @Test fun toString_is_equivalent_to_data_class_toString() { 63 | val dataApi = Sample( 64 | int = 99, 65 | requiredString = "test", 66 | optionalString = null 67 | ) 68 | 69 | val data = DataSample( 70 | int = 99, 71 | requiredString = "test", 72 | optionalString = null 73 | ) 74 | 75 | assertThat(dataApi.toString()).isEqualTo(data.toString().removePrefix("Data")) 76 | } 77 | 78 | /** 79 | * Data class equivalent to [Sample]. 80 | */ 81 | private data class DataSample( 82 | val int: Int, 83 | val requiredString: String, 84 | val optionalString: String? 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /sample/properties.gradle: -------------------------------------------------------------------------------- 1 | // Copies main project Gradle properties to sample project. Can't use buildSrc because settings.gradle 2 | // must access these properties. 3 | 4 | List propertiesFiles = [ 5 | "../gradle.properties", 6 | "../local.properties", 7 | ] 8 | 9 | propertiesFiles.each { fileName -> 10 | File file = file(fileName) 11 | if (file.exists()) { 12 | Properties properties = new Properties() 13 | file.withInputStream { properties.load(it) } 14 | 15 | int moduleNameStartIndex = fileName.indexOf('/') + 1 16 | int moduleNameEndIndex = fileName.lastIndexOf('/') 17 | String namespace 18 | if (moduleNameStartIndex < moduleNameEndIndex) { 19 | namespace = fileName.substring(moduleNameStartIndex, moduleNameEndIndex) 20 | .replace('/', '.') 21 | } else { 22 | namespace = null 23 | } 24 | 25 | properties.each { key, value -> 26 | String namespacedKey 27 | if (namespace == null) { 28 | namespacedKey = key 29 | } else { 30 | namespacedKey = "$namespace.$key" 31 | } 32 | try { 33 | properties.set(namespacedKey, value) 34 | } catch (MissingMethodException ignored) { 35 | // We are in a pluginManagement block that can't set properties, so set an extra instead: 36 | ext.set(namespacedKey, value) 37 | } 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /sample/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val isCi = System.getenv()["CI"] == "true" 3 | 4 | apply(from = "properties.gradle") 5 | 6 | if (!isCi) { 7 | includeBuild("../.") 8 | } 9 | 10 | repositories { 11 | if (isCi) { 12 | logger.lifecycle("Resolving buildscript Poko dependencies from MavenLocal") 13 | exclusiveContent { 14 | forRepository { mavenLocal() } 15 | filter { 16 | val publishGroup = extra["PUBLISH_GROUP"] as String 17 | includeGroup(publishGroup) 18 | } 19 | } 20 | } 21 | mavenCentral() 22 | google() 23 | 24 | if (extra.has("kotlin_dev_repository")) { 25 | val kotlinDevRepository = extra["kotlin_dev_repository"]!! 26 | logger.lifecycle("Adding <$kotlinDevRepository> repository for plugins") 27 | maven { url = uri(kotlinDevRepository) } 28 | } 29 | } 30 | 31 | resolutionStrategy { 32 | val publishVersion = extra["PUBLISH_VERSION"] as String 33 | eachPlugin { 34 | if (requested.id.id == "dev.drewhamilton.poko") { 35 | useVersion(publishVersion) 36 | } 37 | } 38 | } 39 | } 40 | 41 | rootProject.name = "PokoSample" 42 | 43 | include(":jvm") 44 | include(":mpp") 45 | 46 | // Android requires JDK 17; skip it on CI tests for lower JDKs 47 | private val ciJavaVersion = System.getenv()["ci_java_version"]?.toInt() 48 | if (ciJavaVersion == null || ciJavaVersion >= 17) { 49 | include(":compose") 50 | } else { 51 | logger.lifecycle("Testing on JDK $ciJavaVersion; skipping :compose module") 52 | } 53 | 54 | private val isCi = System.getenv()["CI"] == "true" 55 | if (!isCi) { 56 | // Use local Poko modules for non-CI builds: 57 | includeBuild("../.") { 58 | logger.lifecycle("Replacing Poko module dependencies with local projects") 59 | val publishGroup: String = extra["PUBLISH_GROUP"] as String 60 | dependencySubstitution { 61 | substitute(module("$publishGroup:${extra["poko-annotations.POM_ARTIFACT_ID"]}")) 62 | .using(project(":poko-annotations")) 63 | .because("Developers can see local changes reflected in the sample project") 64 | substitute(module("$publishGroup:${extra["poko-compiler-plugin.POM_ARTIFACT_ID"]}")) 65 | .using(project(":poko-compiler-plugin")) 66 | .because("Developers can see local changes reflected in the sample project") 67 | substitute(module("$publishGroup:${extra["poko-gradle-plugin.POM_ARTIFACT_ID"]}")) 68 | .using(project(":poko-gradle-plugin")) 69 | .because("Developers can see local changes reflected in the sample project") 70 | } 71 | } 72 | } 73 | 74 | dependencyResolutionManagement { 75 | versionCatalogs { 76 | create("libs") { 77 | from(files("../gradle/libs.versions.toml")) 78 | 79 | //region Duplicated in buildSrc/settings.gradle 80 | fun String.nullIfBlank(): String? = if (isNullOrBlank()) null else this 81 | 82 | // Compile sample project with different Kotlin version than Poko, if provided: 83 | val kotlinVersionOverride = System.getenv()["poko_sample_kotlin_version"]?.nullIfBlank() 84 | kotlinVersionOverride?.let { kotlinVersion -> 85 | version("kotlin", kotlinVersion) 86 | } 87 | //endregion 88 | } 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-support") 3 | 4 | //region TODO: Move this to build-support 5 | val file: File = file("gradle.properties") 6 | val properties = java.util.Properties() 7 | file.inputStream().use { 8 | properties.load(it) 9 | } 10 | properties.forEach { (key, value) -> 11 | extra[key.toString()] = value 12 | } 13 | //endregion 14 | 15 | repositories { 16 | mavenCentral() 17 | 18 | // KSP: 19 | google() 20 | 21 | // buildconfig plugin: 22 | gradlePluginPortal() 23 | 24 | if (extra.has("kotlin_dev_repository")) { 25 | val kotlinDevRepository = extra["kotlin_dev_repository"]!! 26 | logger.lifecycle("Adding <$kotlinDevRepository> repository for plugins") 27 | maven { url = uri(kotlinDevRepository) } 28 | } 29 | } 30 | } 31 | 32 | plugins { 33 | id("dev.drewhamilton.poko.settings") 34 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 35 | } 36 | 37 | rootProject.name = "Poko" 38 | 39 | include( 40 | ":poko-compiler-plugin", 41 | ":poko-annotations", 42 | ":poko-gradle-plugin", 43 | ":poko-tests", 44 | ":poko-tests:performance", 45 | ) 46 | --------------------------------------------------------------------------------