├── .github └── workflows │ ├── ci.yml │ ├── clear-caches.yml │ └── docs.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── RELEASING.md ├── build-logic ├── convention │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── com.livefront.sealedenum.detekt.gradle.kts │ │ ├── com.livefront.sealedenum.kotlin.gradle.kts │ │ └── com.livefront.sealedenum.publish.gradle.kts └── settings.gradle.kts ├── build.gradle.kts ├── config └── detekt │ └── detekt.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jacoco └── build.gradle.kts ├── ksp ├── api │ └── ksp.api ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── livefront │ └── sealedenum │ └── internal │ └── ksp │ ├── GenSealedEnumHolder.kt │ ├── InvalidSubclassVisibilityException.kt │ ├── KotlinPoetKsp.kt │ ├── KspUtils.kt │ ├── NonObjectSealedSubclassException.kt │ ├── SealedEnumProcessor.kt │ └── SuperInterfaces.kt ├── processing-common ├── api │ └── processing-common.api ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── livefront │ │ └── sealedenum │ │ └── internal │ │ └── common │ │ ├── SealedClassNode.kt │ │ ├── TreeUtils.kt │ │ ├── Unique.kt │ │ ├── Visibility.kt │ │ └── spec │ │ ├── CamelCase.kt │ │ ├── EnumForSealedEnumTypeSpec.kt │ │ ├── EnumSealedObjectPropertySpec.kt │ │ ├── MaybeAddOriginatingElement.kt │ │ ├── SealedClassExtensions.kt │ │ ├── SealedEnumEnumPropertySpec.kt │ │ ├── SealedEnumFileSpec.kt │ │ ├── SealedEnumNamePropertySpec.kt │ │ ├── SealedEnumOrdinalPropertySpec.kt │ │ ├── SealedEnumSealedEnumPropertySpec.kt │ │ ├── SealedEnumTypeSpec.kt │ │ ├── SealedEnumValueOfFunSpec.kt │ │ └── SealedEnumValuesPropertySpec.kt │ └── test │ └── kotlin │ └── com │ └── livefront │ └── sealedenum │ └── internal │ └── common │ └── TreeUtilsTests.kt ├── processing-tests ├── common │ └── test │ │ ├── java │ │ └── com │ │ │ └── livefront │ │ │ └── sealedenum │ │ │ └── compilation │ │ │ └── visibility │ │ │ ├── JavaPrivateInterfaceOuterClass.java │ │ │ ├── JavaPrivateInterfaceSubclass.java │ │ │ ├── JavaProtectedInterfaceBaseClass.java │ │ │ ├── JavaProtectedInterfaceSubclass.java │ │ │ └── subpackage │ │ │ ├── JavaProtectedInterfaceBaseClass.java │ │ │ └── JavaProtectedInterfaceSubclass.java │ │ └── kotlin │ │ └── com │ │ └── livefront │ │ └── sealedenum │ │ ├── compilation │ │ ├── basic │ │ │ ├── EmptySealedClass.kt │ │ │ ├── EmptySealedClassTests.kt │ │ │ ├── EmptySealedInterface.kt │ │ │ ├── EmptySealedInterfaceTests.kt │ │ │ ├── OneObjectSealedClass.kt │ │ │ ├── OneObjectSealedClassTests.kt │ │ │ ├── OneObjectSealedInterface.kt │ │ │ ├── OneObjectSealedInterfaceTests.kt │ │ │ ├── TwoObjectSealedClass.kt │ │ │ ├── TwoObjectSealedClassTests.kt │ │ │ ├── TwoObjectSealedInterface.kt │ │ │ └── TwoObjectSealedInterfaceTests.kt │ │ ├── generics │ │ │ ├── GenericSealedClass.kt │ │ │ ├── GenericSealedClassTests.kt │ │ │ ├── SealedEnumWithAbstractBaseClasses.kt │ │ │ ├── SealedEnumWithAbstractBaseClassesTests.kt │ │ │ ├── SealedEnumWithInterfaces.kt │ │ │ └── SealedEnumWithInterfacesTests.kt │ │ ├── hierarchy │ │ │ ├── SealedClassHierarchy.kt │ │ │ ├── SealedClassHierarchyTests.kt │ │ │ ├── SealedInterfaceHierarchy.kt │ │ │ └── SealedInterfaceHierarchyTests.kt │ │ ├── location │ │ │ ├── NestedClass.kt │ │ │ ├── NestedClassTests.kt │ │ │ ├── OutsideSealedClass.kt │ │ │ ├── OutsideSealedClassTests.kt │ │ │ ├── SplitAcrossFilesSealedClass.kt │ │ │ ├── SplitAcrossFilesSealedClassTests.kt │ │ │ ├── SplitAcrossFilesSubclassA.kt │ │ │ ├── SplitAcrossFilesSubclassB.kt │ │ │ └── SplitAcrossFilesSubclassC.kt │ │ ├── traversal │ │ │ ├── TraversalOrder.kt │ │ │ └── TraversalOrderTests.kt │ │ ├── usecases │ │ │ ├── EnvironmentsSealedEnum.kt │ │ │ ├── EnvironmentsSealedEnumTests.kt │ │ │ ├── Flag.kt │ │ │ ├── FlagTests.kt │ │ │ ├── MultiInterfaceFlag.kt │ │ │ └── MultiInterfaceFlagTests.kt │ │ └── visibility │ │ │ ├── PrivateInterfaceSealedClass.kt │ │ │ ├── PrivateInterfaceSealedClassTests.kt │ │ │ ├── ProtectedInterfaceSealedClass.kt │ │ │ ├── ProtectedInterfaceSealedClassTests.kt │ │ │ ├── ProtectedInterfaceSealedClassWithDifferentPackageBaseClass.kt │ │ │ ├── ProtectedInterfaceSealedClassWithDifferentPackageBaseClassTests.kt │ │ │ ├── VisibilitySealedClass.kt │ │ │ └── VisibilitySealedClassTests.kt │ │ ├── errors │ │ ├── AnnotationErrors.kt │ │ └── NonObjectErrors.kt │ │ └── testing │ │ ├── CompilationAssertions.kt │ │ ├── PathsUtils.kt │ │ └── ProcessingType.kt ├── ksp-tests │ ├── build.gradle.kts │ └── src │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── livefront │ │ └── sealedenum │ │ ├── compilation │ │ └── ksp │ │ │ ├── NestedObjectsWithSameName.kt │ │ │ └── NestedObjectsWithSameNameTests.kt │ │ └── testing │ │ ├── Compilation.kt │ │ └── CurrentProcessingType.kt └── processor-tests │ ├── build.gradle.kts │ └── src │ └── test │ ├── java │ └── com │ │ └── livefront │ │ └── sealedenum │ │ └── compilation │ │ └── kitchensink │ │ ├── JavaFirstBaseClass.java │ │ ├── JavaInterface1.java │ │ ├── JavaInterface2.java │ │ ├── JavaInterface3.java │ │ ├── JavaInterface4.java │ │ ├── JavaInterface5.java │ │ └── JavaSecondBaseClass.java │ └── kotlin │ └── com │ └── livefront │ └── sealedenum │ ├── compilation │ └── kitchensink │ │ ├── JavaBaseClasses.kt │ │ └── JavaBaseClassesTests.kt │ └── testing │ ├── Compilation.kt │ └── CurrentProcessingType.kt ├── processor ├── api │ └── processor.api ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── livefront │ └── sealedenum │ └── internal │ └── processor │ ├── ClassInspectorUtil.kt │ ├── InvalidSubclassVisibilityException.kt │ ├── NonObjectSealedSubclassException.kt │ ├── SealedEnumProcessor.kt │ ├── SuperInterfaces.kt │ └── WildcardedTypeParameters.kt ├── runtime ├── api │ └── runtime.api ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── livefront │ │ └── sealedenum │ │ ├── EnumForSealedEnumProvider.kt │ │ ├── GenSealedEnum.kt │ │ ├── SealedEnum.kt │ │ ├── SealedEnumWithEnumProvider.kt │ │ └── TreeTraversalOrder.kt │ └── test │ └── kotlin │ └── com │ └── livefront │ └── sealedenum │ └── CreateSealedEnumFromEnumTests.kt └── settings.gradle.kts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.os }} 10 | 11 | concurrency: 12 | group: ci-${{ github.event_name }}-${{ github.ref }}-${{ matrix.java-version }}-${{ matrix.os }} 13 | cancel-in-progress: true 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | java-version: 19 | - 1.8 20 | - 11 21 | - 17 22 | os: 23 | - ubuntu-latest 24 | - macOS-latest 25 | - windows-latest 26 | 27 | steps: 28 | - name: Set TMP environment variable on Windows 29 | if: matrix.os == 'windows-latest' 30 | run: echo "TMP=${{ runner.temp }}" >> $env:GITHUB_ENV 31 | 32 | - name: Checkout 33 | uses: actions/checkout@v2 34 | 35 | - name: Validate Gradle Wrapper 36 | uses: gradle/wrapper-validation-action@v1 37 | 38 | - name: Setup Gradle 39 | uses: gradle/gradle-build-action@v2 40 | 41 | - name: Configure Ruby 42 | uses: ruby/setup-ruby@v1 43 | with: 44 | ruby-version: '2.7.2' 45 | bundler-cache: true 46 | 47 | - name: Configure JDK 48 | uses: actions/setup-java@v1 49 | with: 50 | java-version: ${{ matrix.java-version }} 51 | 52 | - name: Test 53 | run: ./gradlew check jacocoTestReport --stacktrace --info 54 | 55 | - name: Danger 56 | # Run Danger for PRs originating from within the repo (for fork PRs the token won't give permission to comment) 57 | if: github.event_name == 'pull_request' && 58 | matrix.os == 'ubuntu-latest' && 59 | matrix.java-version == '1.8' && 60 | github.event.pull_request.head.repo.full_name == github.repository 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | run: bundle exec danger 64 | -------------------------------------------------------------------------------- /.github/workflows/clear-caches.yml: -------------------------------------------------------------------------------- 1 | name: clear-caches 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | clear: 7 | name: Clear all caches 8 | runs-on: ubuntu-latest 9 | steps: 10 | - run: | 11 | # Keep track of the total number of caches deleted 12 | TOTAL_COUNT=0 13 | # The cache list is paginated, so we won't necessarily get all ids with the first call 14 | while true; do 15 | LIST_CACHES=$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository }}/actions/caches) 16 | REMAINING_COUNT=$(echo $LIST_CACHES | jq .total_count) 17 | # If there are no more, we're done! 18 | [[ $REMAINING_COUNT > 0 ]] || break 19 | # Parse out the list of ids 20 | IDS=$(echo $LIST_CACHES | jq .actions_caches[].id) 21 | for ID in ${IDS[@]}; do 22 | echo "Clearing $ID" 23 | gh api --method DELETE -H "Accept: application/vnd.github+json" /repos/${{ github.repository }}/actions/caches/$ID 24 | TOTAL_COUNT=$((TOTAL_COUNT + 1)) 25 | done 26 | done 27 | echo "Cleared $TOTAL_COUNT caches" 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | java-version: 15 | - 1.8 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Validate Gradle Wrapper 22 | uses: gradle/wrapper-validation-action@v1 23 | 24 | - name: Setup Gradle 25 | uses: gradle/gradle-build-action@v2 26 | 27 | - name: Configure JDK 28 | uses: actions/setup-java@v1 29 | with: 30 | java-version: ${{ matrix.java-version }} 31 | 32 | - name: Create documentation 33 | run: ./gradlew dokkaHtmlMultiModule 34 | 35 | - name: Deploy to gh-pages 36 | uses: peaceiris/actions-gh-pages@v3 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | publish_dir: ./build/dokka/htmlMultiModule 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ files 2 | .idea/ 3 | out/ 4 | *.iml 5 | 6 | # Gradle files 7 | build/ 8 | .gradle/ 9 | 10 | local.properties 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.7.0 2 | ## Bug fixes: 3 | - Switch to `is` instead of `==` to improve static initialization behavior (#130). Thanks @kyay10! 4 | - Ensure objects are not duplicated in the sealed enum list, if reachable from more than one subclass (#135). Thanks @kyay10! 5 | 6 | ## Breaking changes: 7 | - The type of `EnumForSealedEnumProvider.enumClass` is updated from a `Class` to a `KClass`. 8 | This allows `runtime` to not depend on any JVM classes, and therefore can be a multiplatform artifact in the future. 9 | 10 | # 0.6.0 11 | ## Features: 12 | - Update to Kotlin 1.8 13 | 14 | ## Miscellaneous updates: 15 | - Various dependency updates 16 | - Updated JDK versions on CI to 1.8, 11 and 17 17 | 18 | # 0.5.0 19 | ## Features: 20 | - Update to Kotlin 1.7 21 | 22 | ## Breaking changes: 23 | - The implicit order of sealed subclasses was changed in Kotlin 1.7, resulting in different ordinal orders when using `sealed-enum` `0.4.0` and below. 24 | `sealed-enum` `0.5.0` restored the expected behavior when declaring subclasses as nested inner classes and defined a deterministic order when subclasses occur elsewhere in the file and package. 25 | 26 | ## Miscellaneous updates: 27 | - Various dependency updates 28 | - Switch to using version catalogs and convention plugins 29 | 30 | # 0.4.0 31 | ## Features: 32 | - Added full support for `sealed interface`s 33 | - Added [KSP](https://github.com/google/ksp) processor as an alternate code generation method 34 | 35 | ## Miscellaneous updates: 36 | - Update to Kotlin 1.5 37 | - Add binary compatibility validator 38 | - Added JDK 15 to CI tests 39 | - Added CI to run on macOS and Windows 40 | - Various dependency updates 41 | 42 | # 0.3.0 43 | ## Breaking changes: 44 | - Artifact ids were updated (`sealedenum` -> `runtime` and `sealedenumprocessor` -> `processor`). 45 | This was done to accommodate alternate generation implementations, such as by [KSP](https://github.com/google/ksp) 46 | 47 | ## Miscellaneous updates: 48 | - Switch to GitHub actions for CI 49 | - Various dependency updates 50 | 51 | # 0.2.0 52 | ## Features: 53 | - Added extension properties and methods to make `sealed-enum` easier to use in common cases. 54 | In particular, access to `values`, ordinals, names and the `SealedEnum` implementation can all be achieved without having to use a generated class name. 55 | - Added support for isomorphic enum generation via the `generateEnum` argument for `@GenSealedEnum`. 56 | Isomorphism and class information can be retrieved from the implemented `EnumForSealedEnumProvider` interface. 57 | 58 | ## Breaking changes: 59 | - `@GenSealedEnum` must now be applied to the companion object of `sealed class`, rather than the `sealed class` itself. 60 | - Processor option for auto-generation without `@GenSealedEnum` was removed. 61 | 62 | ## Miscellaneous updates: 63 | - Updated to Kotlin 1.4 64 | - Switched to [`kotlin-compile-testing`](https://github.com/tschuchortdev/kotlin-compile-testing) for testing 65 | - Updated dependencies and created `buildSrc` for version management 66 | - Generated code is explicit-api-mode compatible by default (courtesy of KotlinPoet 1.7.x) 67 | 68 | # 0.1.1 69 | Publish dokka documentation and sources 70 | 71 | # 0.1.0 72 | Initial preview release 73 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to sealed-enum 2 | 3 | Contributions to the project are welcome! All external contributors should fork the repository and submit pull requests against the `main` branch. These must pass the checks run by CI and be reviewed and approved by a primary contributor before they can be merged. Prior to merging, the branch should be rebased with the latest changes from `main` and commits should be squashed where appropriate in order to cleanly encapsulate the final set of changes (i.e. no "Addressed review comment" commits should be present). 4 | 5 | Please try to ensure that the project's existing code style and conventions are followed when making new contributions. To automate this process, this project uses [`detekt`](https://github.com/arturbosch/detekt) and its bundled `ktlint` for linting. 6 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | shroud.report 'jacoco/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml', 80, 80, true 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem 'danger' 6 | gem 'danger-shroud' 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.0) 5 | public_suffix (>= 2.0.2, < 5.0) 6 | claide (1.1.0) 7 | claide-plugins (0.9.2) 8 | cork 9 | nap 10 | open4 (~> 1.3) 11 | colored2 (3.1.2) 12 | cork (0.3.0) 13 | colored2 (~> 3.1) 14 | danger (8.6.1) 15 | claide (~> 1.0) 16 | claide-plugins (>= 0.9.2) 17 | colored2 (~> 3.1) 18 | cork (~> 0.1) 19 | faraday (>= 0.9.0, < 2.0) 20 | faraday-http-cache (~> 2.0) 21 | git (~> 1.7) 22 | kramdown (~> 2.3) 23 | kramdown-parser-gfm (~> 1.0) 24 | no_proxy_fix 25 | octokit (~> 4.7) 26 | terminal-table (>= 1, < 4) 27 | danger-plugin-api (1.0.0) 28 | danger (> 2.0) 29 | danger-shroud (0.0.3) 30 | danger-plugin-api (~> 1.0) 31 | nokogiri 32 | faraday (1.10.0) 33 | faraday-em_http (~> 1.0) 34 | faraday-em_synchrony (~> 1.0) 35 | faraday-excon (~> 1.1) 36 | faraday-httpclient (~> 1.0) 37 | faraday-multipart (~> 1.0) 38 | faraday-net_http (~> 1.0) 39 | faraday-net_http_persistent (~> 1.0) 40 | faraday-patron (~> 1.0) 41 | faraday-rack (~> 1.0) 42 | faraday-retry (~> 1.0) 43 | ruby2_keywords (>= 0.0.4) 44 | faraday-em_http (1.0.0) 45 | faraday-em_synchrony (1.0.0) 46 | faraday-excon (1.1.0) 47 | faraday-http-cache (2.4.0) 48 | faraday (>= 0.8) 49 | faraday-httpclient (1.0.1) 50 | faraday-multipart (1.0.4) 51 | multipart-post (~> 2) 52 | faraday-net_http (1.0.1) 53 | faraday-net_http_persistent (1.2.0) 54 | faraday-patron (1.0.0) 55 | faraday-rack (1.0.0) 56 | faraday-retry (1.0.3) 57 | git (1.18.0) 58 | addressable (~> 2.8) 59 | rchardet (~> 1.8) 60 | kramdown (2.4.0) 61 | rexml 62 | kramdown-parser-gfm (1.1.0) 63 | kramdown (~> 2.0) 64 | mini_portile2 (2.8.1) 65 | multipart-post (2.2.3) 66 | nap (1.1.0) 67 | no_proxy_fix (0.1.2) 68 | nokogiri (1.14.2) 69 | mini_portile2 (~> 2.8.0) 70 | racc (~> 1.4) 71 | octokit (4.25.0) 72 | faraday (>= 1, < 3) 73 | sawyer (~> 0.9) 74 | open4 (1.3.4) 75 | public_suffix (4.0.7) 76 | racc (1.6.2) 77 | rchardet (1.8.0) 78 | rexml (3.2.5) 79 | ruby2_keywords (0.0.5) 80 | sawyer (0.9.2) 81 | addressable (>= 2.3.5) 82 | faraday (>= 0.17.3, < 3) 83 | terminal-table (3.0.2) 84 | unicode-display_width (>= 1.1.1, < 3) 85 | unicode-display_width (2.1.0) 86 | 87 | PLATFORMS 88 | ruby 89 | 90 | DEPENDENCIES 91 | danger 92 | danger-shroud 93 | 94 | BUNDLED WITH 95 | 2.1.4 96 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | 1. A new version should be determined by following the [semantic versioning](https://semver.org/) scheme. 4 | 1. Create a new branch called `prepare-xx.xx.xx-release` using the updated version number. 5 | 1. Update the version number in the CHANGELOG and document all appropriate changes since the last release. 6 | 1. Update the version number where appropriate in the README (if applicable). 7 | 1. Create and merge a Pull Request for this branch. 8 | 1. In GitHub, go to [https://github.com/livefront/sealed-enum/releases] and create a new release with "Draft a new release". 9 | -------------------------------------------------------------------------------- /build-logic/convention/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | `kotlin-dsl` 5 | } 6 | 7 | group = "com.livefront.sealedenum.buildlogic" 8 | 9 | java { 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | } 13 | 14 | tasks.withType().configureEach { 15 | kotlinOptions { 16 | jvmTarget = JavaVersion.VERSION_1_8.toString() 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation(kotlin("gradle-plugin", libs.versions.kotlin.get())) 22 | implementation(libs.detekt.gradlePlugin) 23 | implementation(libs.dokka.gradlePlugin) 24 | implementation(libs.kotlinx.binaryCompatabilityValidator.gradlePlugin) 25 | } 26 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com.livefront.sealedenum.detekt.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.gitlab.arturbosch.detekt") 3 | } 4 | 5 | val libs = extensions.getByType().named("libs") 6 | 7 | detekt { 8 | buildUponDefaultConfig = true 9 | allRules = true 10 | autoCorrect = System.getenv("CI") != "true" 11 | config.setFrom("$rootDir/config/detekt/detekt.yml") 12 | } 13 | 14 | dependencies { 15 | detektPlugins(libs.findLibrary("detekt.formatting").get()) 16 | } 17 | 18 | tasks { 19 | withType { 20 | jvmTarget = JavaVersion.VERSION_1_8.toString() 21 | } 22 | 23 | getByName("check") { 24 | dependsOn("detektMain") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com.livefront.sealedenum.kotlin.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | jacoco 4 | } 5 | 6 | val libs = extensions.getByType().named("libs") 7 | 8 | java { 9 | sourceCompatibility = JavaVersion.VERSION_1_8 10 | targetCompatibility = JavaVersion.VERSION_1_8 11 | } 12 | 13 | kotlin { 14 | explicitApi() 15 | } 16 | 17 | jacoco { 18 | toolVersion = libs.findVersion("jacoco").get().toString() 19 | } 20 | 21 | tasks { 22 | test { 23 | useJUnitPlatform() 24 | } 25 | 26 | jacocoTestReport { 27 | dependsOn(test) 28 | 29 | reports { 30 | html.required.set(true) 31 | xml.required.set(true) 32 | } 33 | } 34 | 35 | withType().configureEach { 36 | kotlinOptions { 37 | allWarningsAsErrors = true 38 | freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn" 39 | jvmTarget = JavaVersion.VERSION_1_8.toString() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com.livefront.sealedenum.publish.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins{ 2 | kotlin("jvm") 3 | `maven-publish` 4 | id("org.jetbrains.dokka") 5 | id("org.jetbrains.kotlinx.binary-compatibility-validator") 6 | } 7 | 8 | apiValidation { 9 | ignoredPackages.add("com.livefront.sealedenum.internal") 10 | } 11 | 12 | tasks { 13 | dokkaHtml { 14 | outputDirectory.set(javadoc.get().destinationDir) 15 | } 16 | } 17 | 18 | val sourcesJar by tasks.creating(Jar::class) { 19 | archiveClassifier.set("sources") 20 | from(sourceSets.main.get().allSource) 21 | } 22 | 23 | val javadocJar by tasks.creating(Jar::class) { 24 | archiveClassifier.set("javadoc") 25 | from(tasks.dokkaHtml) 26 | } 27 | 28 | publishing { 29 | publications { 30 | create("default") { 31 | from(components["java"]) 32 | artifact(sourcesJar) 33 | artifact(javadocJar) 34 | 35 | pom { 36 | name.set(project.name) 37 | description.set("Obsoleting enums with sealed classes of objects") 38 | url.set("https://github.com/livefront/sealed-enum") 39 | 40 | licenses { 41 | license { 42 | name.set("The Apache Software License, Version 2.0") 43 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 44 | distribution.set("repo") 45 | } 46 | } 47 | 48 | developers { 49 | developer { 50 | id.set("alexvanyo") 51 | name.set("Alex Vanyo") 52 | organization.set("Livefront") 53 | organizationUrl.set("https://www.livefront.com") 54 | } 55 | } 56 | 57 | scm { 58 | url.set("https://github.com/livefront/sealed-enum") 59 | connection.set("scm:git:git://github.com/livefront/sealed-enum.git") 60 | developerConnection.set("scm:git:git://github.com/livefront/sealed-enum.git") 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositories { 11 | mavenCentral() 12 | } 13 | versionCatalogs { 14 | create("libs") { 15 | from(files("../gradle/libs.versions.toml")) 16 | } 17 | } 18 | } 19 | 20 | include(":convention") 21 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) apply false 3 | alias(libs.plugins.dokka) 4 | } 5 | -------------------------------------------------------------------------------- /config/detekt/detekt.yml: -------------------------------------------------------------------------------- 1 | formatting: 2 | MaximumLineLength: 3 | excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ] 4 | 5 | style: 6 | ForbiddenComment: 7 | values: ['FIXME:', 'STOPSHIP:'] 8 | MaxLineLength: 9 | excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ] 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | 17 | group=com.livefront.sealedenum 18 | version=0.7.0 19 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | autoService = "1.0" 3 | detekt = "1.22.0" 4 | dokka = "1.8.10" 5 | incap = "0.3" 6 | jacoco = "0.8.8" 7 | junit = "5.8.1" 8 | kotlin = "1.8.0" 9 | kotlinxBinaryCompatibilityValidator = "0.13.0" 10 | kotlinCompileTesting = "1.5.0" 11 | kotlinPoet = "1.13.0" 12 | ksp = "1.8.0-1.0.9" 13 | 14 | [libraries] 15 | autoService-runtime = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoService" } 16 | autoService-processor = { module = "com.google.auto.service:auto-service", version.ref = "autoService" } 17 | 18 | detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt"} 19 | detekt-gradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } 20 | 21 | dokka-gradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } 22 | 23 | incap-runtime = { module = "net.ltgt.gradle.incap:incap", version.ref = "incap" } 24 | incap-processor = { module = "net.ltgt.gradle.incap:incap-processor", version.ref = "incap" } 25 | 26 | junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } 27 | 28 | kotlinCompileTesting-base = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kotlinCompileTesting" } 29 | kotlinCompileTesting-ksp = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kotlinCompileTesting" } 30 | 31 | kotlinx-binaryCompatabilityValidator-gradlePlugin = { module = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin", version.ref = "kotlinxBinaryCompatibilityValidator" } 32 | 33 | squareUp-kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet" } 34 | squareUp-kotlinPoetKsp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet" } 35 | squareUp-kotlinPoetMetadata = { module = "com.squareup:kotlinpoet-metadata", version.ref = "kotlinPoet" } 36 | 37 | ksp-runtime = { module = "com.google.devtools.ksp:symbol-processing", version.ref = "ksp" } 38 | ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } 39 | 40 | [plugins] 41 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 42 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 43 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 44 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livefront/sealed-enum/71a0c5c445b3b86d2f3d6e15d070784183f40dc0/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.0.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /jacoco/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | base 3 | id("jacoco-report-aggregation") 4 | } 5 | 6 | dependencies { 7 | jacocoAggregation(projects.ksp) 8 | jacocoAggregation(projects.processingCommon) 9 | jacocoAggregation(projects.processingTests.kspTests) 10 | jacocoAggregation(projects.processingTests.processorTests) 11 | jacocoAggregation(projects.processor) 12 | jacocoAggregation(projects.runtime) 13 | } 14 | 15 | reporting { 16 | reports { 17 | val testCodeCoverageReport by creating(JacocoCoverageReport::class) { 18 | testType.set(TestSuiteType.UNIT_TEST) 19 | } 20 | } 21 | } 22 | 23 | tasks.check { 24 | dependsOn(tasks.named("testCodeCoverageReport")) 25 | } 26 | -------------------------------------------------------------------------------- /ksp/api/ksp.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livefront/sealed-enum/71a0c5c445b3b86d2f3d6e15d070784183f40dc0/ksp/api/ksp.api -------------------------------------------------------------------------------- /ksp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.livefront.sealedenum.kotlin") 3 | id("com.livefront.sealedenum.detekt") 4 | id("com.livefront.sealedenum.publish") 5 | kotlin("kapt") 6 | } 7 | 8 | dependencies { 9 | implementation(projects.runtime) 10 | implementation(projects.processingCommon) 11 | implementation(libs.squareUp.kotlinPoet) 12 | implementation(libs.squareUp.kotlinPoetKsp) 13 | compileOnly(libs.ksp.api) 14 | implementation(libs.autoService.runtime) 15 | kapt(libs.autoService.processor) 16 | } 17 | 18 | kapt { 19 | includeCompileClasspath = false 20 | } 21 | -------------------------------------------------------------------------------- /ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/GenSealedEnumHolder.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.ksp 2 | 3 | import com.google.devtools.ksp.symbol.KSAnnotation 4 | import com.livefront.sealedenum.GenSealedEnum 5 | import com.livefront.sealedenum.TreeTraversalOrder 6 | 7 | /** 8 | * A constructable version of [GenSealedEnum]. 9 | */ 10 | public data class GenSealedEnumHolder( 11 | val traversalOrder: TreeTraversalOrder, 12 | val generateEnum: Boolean, 13 | ) { 14 | 15 | public companion object { 16 | public fun fromKSAnnotation(ksAnnotation: KSAnnotation): GenSealedEnumHolder { 17 | val traversalOrderQualifiedName = ksAnnotation.arguments.find { 18 | it.name?.asString() == "traversalOrder" 19 | }?.value.toString() 20 | 21 | @Suppress("SwallowedException") 22 | val traversalOrder = try { 23 | @Suppress("UnsafeCallOnNullableType") 24 | TreeTraversalOrder.valueOf( 25 | traversalOrderQualifiedName.removePrefix( 26 | "${TreeTraversalOrder::class.qualifiedName!!}." 27 | ) 28 | ) 29 | } catch (exception: IllegalArgumentException) { 30 | TreeTraversalOrder.IN_ORDER 31 | } 32 | 33 | val generateEnum = ksAnnotation.arguments.find { 34 | it.name?.asString() == "generateEnum" 35 | }?.value.toString().toBoolean() 36 | 37 | return GenSealedEnumHolder( 38 | traversalOrder, 39 | generateEnum 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/InvalidSubclassVisibilityException.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.ksp 2 | 3 | import com.google.devtools.ksp.symbol.KSNode 4 | 5 | /** 6 | * An exception that indicates that a sealed subclass has an invalid visibility. 7 | */ 8 | public class InvalidSubclassVisibilityException(public val ksNode: KSNode) : Exception() 9 | -------------------------------------------------------------------------------- /ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/KotlinPoetKsp.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.ksp 2 | 3 | import com.google.devtools.ksp.symbol.KSClassDeclaration 4 | import com.google.devtools.ksp.symbol.Variance 5 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 6 | import com.squareup.kotlinpoet.STAR 7 | import com.squareup.kotlinpoet.TypeName 8 | import com.squareup.kotlinpoet.ksp.toClassName 9 | import com.squareup.kotlinpoet.ksp.toTypeName 10 | 11 | internal fun KSClassDeclaration.toTypeName(argumentList: List = emptyList()): TypeName { 12 | val className = toClassName() 13 | return if (argumentList.isNotEmpty()) { 14 | className.parameterizedBy(argumentList) 15 | } else { 16 | className 17 | } 18 | } 19 | 20 | /** 21 | * A mapping of [KSClassDeclaration.typeParameters] to [TypeName]s. 22 | * 23 | * Rather than returning variable names, they are converted into a [TypeName] that can be directly specified in an 24 | * interface. 25 | * 26 | * In particular, single invariant types can be replaced with the type directly. In all other cases (multiple bounds, 27 | * other types of variance) the only thing we can do is wildcard the type. 28 | */ 29 | internal fun KSClassDeclaration.wildcardedTypeNames(): List = 30 | typeParameters.map { 31 | val bounds = it.bounds.toList() 32 | 33 | if (bounds.size == 1) { 34 | when (it.variance) { 35 | Variance.INVARIANT -> bounds[0].resolve().toTypeName() 36 | else -> STAR 37 | } 38 | } else { 39 | STAR 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/KspUtils.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.ksp 2 | 3 | import com.google.devtools.ksp.symbol.KSAnnotated 4 | import com.google.devtools.ksp.symbol.KSAnnotation 5 | import com.google.devtools.ksp.symbol.KSClassDeclaration 6 | import com.google.devtools.ksp.symbol.KSType 7 | 8 | internal fun KSClassDeclaration.asType(): KSType = asType(emptyList()) 9 | 10 | internal fun KSAnnotated.findAnnotationsWithType(target: KSType): Sequence = 11 | annotations.filter { it.annotationType.resolve() == target } 12 | -------------------------------------------------------------------------------- /ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/NonObjectSealedSubclassException.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.ksp 2 | 3 | import com.google.devtools.ksp.symbol.KSNode 4 | 5 | /** 6 | * An exception that indicates that sealed class has a non-object subclass. 7 | */ 8 | public class NonObjectSealedSubclassException(public val ksNode: KSNode) : Exception() 9 | -------------------------------------------------------------------------------- /ksp/src/main/kotlin/com/livefront/sealedenum/internal/ksp/SuperInterfaces.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.ksp 2 | 3 | import com.google.devtools.ksp.isInternal 4 | import com.google.devtools.ksp.isJavaPackagePrivate 5 | import com.google.devtools.ksp.isProtected 6 | import com.google.devtools.ksp.isPublic 7 | import com.google.devtools.ksp.symbol.ClassKind 8 | import com.google.devtools.ksp.symbol.KSClassDeclaration 9 | import com.google.devtools.ksp.symbol.KSType 10 | import com.google.devtools.ksp.symbol.KSTypeParameter 11 | import com.google.devtools.ksp.symbol.Modifier 12 | import com.google.devtools.ksp.symbol.Origin 13 | import com.squareup.kotlinpoet.ClassName 14 | import com.squareup.kotlinpoet.ParameterizedTypeName 15 | import com.squareup.kotlinpoet.STAR 16 | import com.squareup.kotlinpoet.TypeName 17 | import com.squareup.kotlinpoet.WildcardTypeName 18 | 19 | /** 20 | * An internal context with which super interfaces can be determined. 21 | */ 22 | internal class SuperInterfaces( 23 | private val sealedEnumKSClass: KSClassDeclaration 24 | ) { 25 | 26 | /** 27 | * Returns a list of all valid super interfaces that it or any of its superclasses implement. 28 | * 29 | * A super interface is "valid" if it does not require a wildcard generic type parameter and if it is of an 30 | * appropriate visibility. 31 | */ 32 | fun getAllSuperInterfaces(): List { 33 | val superInterfaces = mutableListOf() 34 | 35 | getAllSuperInterfacesImpl( 36 | sealedEnumKSClass, 37 | sealedEnumKSClass.wildcardedTypeNames(), 38 | superInterfaces 39 | ) 40 | 41 | return superInterfaces 42 | } 43 | 44 | /** 45 | * The recursive implementation for [getAllSuperInterfaces]. 46 | * 47 | * This method adds all valid super interfaces to [superInterfaces]. 48 | */ 49 | private tailrec fun getAllSuperInterfacesImpl( 50 | sealedEnumKSClass: KSClassDeclaration, 51 | parentTypeArguments: List, 52 | superInterfaces: MutableList, 53 | ) { 54 | check(sealedEnumKSClass.typeParameters.size == parentTypeArguments.size) 55 | 56 | // Associate the type variable names with their type arguments from the subclass. 57 | val typeVariableNamesToTypeArguments = sealedEnumKSClass.typeParameters 58 | .map { it.name.asString() }.zip(parentTypeArguments).toMap() 59 | 60 | // Add all implement interfaces to our list, taking care to substitute the type names and throwing away invalid 61 | // interfaces 62 | superInterfaces += sealedEnumKSClass.superTypes 63 | .asSequence() 64 | .map { it.resolve() } 65 | .filter { 66 | val declaration = it.declaration 67 | declaration is KSClassDeclaration && 68 | declaration.classKind == ClassKind.INTERFACE && 69 | declaration.isVisibleInterface() && 70 | !declaration.isSealedInterface() 71 | } 72 | .map { it.substituteTypeNames(typeVariableNamesToTypeArguments) } 73 | .filter { it.isValidInterface() } 74 | 75 | val superClassKSType = sealedEnumKSClass.superTypes.firstOrNull()?.resolve() ?: return 76 | val superClassKSDeclaration = superClassKSType.declaration as? KSClassDeclaration ?: return 77 | 78 | getAllSuperInterfacesImpl( 79 | superClassKSDeclaration, 80 | superClassKSType.arguments 81 | .mapNotNull { it.type } 82 | .map { it.resolve() } 83 | .map { it.substituteTypeNames(typeVariableNamesToTypeArguments) }, 84 | superInterfaces 85 | ) 86 | } 87 | 88 | private fun KSType.substituteTypeNames(typeVariableNamesToTypeArguments: Map): TypeName = 89 | when (val declaration = this.declaration) { 90 | is KSClassDeclaration -> declaration.toTypeName( 91 | arguments 92 | .mapNotNull { it.type } 93 | .map { it.resolve() } 94 | .map { it.substituteTypeNames(typeVariableNamesToTypeArguments) } 95 | ).copy(nullable = isMarkedNullable) 96 | is KSTypeParameter -> typeVariableNamesToTypeArguments[declaration.name.asString()] ?: STAR 97 | else -> STAR 98 | } 99 | 100 | /** 101 | * Returns true if the [KSClassDeclaration] interface has the correct visibility to be implemented by a generated 102 | * enum. 103 | */ 104 | private fun KSClassDeclaration.isVisibleInterface(): Boolean = 105 | isPublic() || isInternal() || isJavaPackagePrivate() || 106 | (isProtected() && origin == Origin.JAVA && packageName == sealedEnumKSClass.packageName) 107 | 108 | /** 109 | * Returns true if the [KSClassDeclaration] interface is sealed. 110 | */ 111 | private fun KSClassDeclaration.isSealedInterface(): Boolean = 112 | modifiers.contains(Modifier.SEALED) 113 | 114 | /** 115 | * Returns true if the [TypeName] interface has the correct parameterization (if any) to be implemented by a 116 | * generated enum. 117 | */ 118 | private fun TypeName.isValidInterface(): Boolean = when (this) { 119 | is ClassName -> true 120 | is ParameterizedTypeName -> typeArguments.none { it is WildcardTypeName } 121 | else -> false 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /processing-common/api/processing-common.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livefront/sealed-enum/71a0c5c445b3b86d2f3d6e15d070784183f40dc0/processing-common/api/processing-common.api -------------------------------------------------------------------------------- /processing-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.livefront.sealedenum.kotlin") 3 | id("com.livefront.sealedenum.detekt") 4 | id("com.livefront.sealedenum.publish") 5 | `maven-publish` 6 | } 7 | 8 | dependencies { 9 | implementation(projects.runtime) 10 | implementation(libs.squareUp.kotlinPoet) 11 | 12 | testImplementation(libs.junit.jupiter) 13 | } 14 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/SealedClassNode.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common 2 | 3 | import com.livefront.sealedenum.internal.common.SealedClassNode.Object 4 | import com.livefront.sealedenum.internal.common.SealedClassNode.SealedClass 5 | import com.livefront.sealedenum.internal.common.spec.SealedObject 6 | 7 | /** 8 | * An internal tree structure representing a sealed class and its sealed subclasses. 9 | * 10 | * A node can either be a [Object] (a leaf node) or a [SealedClass] (which might be a non-leaf node). 11 | */ 12 | public sealed class SealedClassNode { 13 | 14 | /** 15 | * The leaf node of the sealed class tree, which represents an object subclass. 16 | * 17 | * The object's name is [className] 18 | */ 19 | public data class Object(public val className: SealedObject) : SealedClassNode() 20 | 21 | /** 22 | * A node representing a subclass sealed class. This node may have additional children in [sealedSubclasses], 23 | * or could be a leaf itself if [sealedSubclasses] is empty. 24 | */ 25 | public data class SealedClass(public val sealedSubclasses: List) : SealedClassNode() 26 | } 27 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/TreeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common 2 | 3 | import com.livefront.sealedenum.TreeTraversalOrder 4 | import com.squareup.kotlinpoet.ClassName 5 | import java.util.ArrayDeque 6 | 7 | /** 8 | * An internal function to traverse the tree rooted at [sealedClassNode] with the prescribed [treeTraversalOrder], 9 | * returning a list of [ClassName]s representing all [SealedClassNode.Object]s in the tree. 10 | * Duplicates are removed, and only the first occurrence of each [ClassName] is preserved. 11 | */ 12 | public fun getSealedObjectsFromNode( 13 | treeTraversalOrder: TreeTraversalOrder, 14 | sealedClassNode: SealedClassNode.SealedClass 15 | ): List { 16 | // Set up the deque for iterative processing. 17 | // For level order traversal, this will be used as a queue, and for all other traversal this will be used as a stack 18 | val nodeDeque = ArrayDeque().apply { 19 | add(sealedClassNode) 20 | } 21 | 22 | val sealedObjects = mutableListOf() 23 | 24 | while (nodeDeque.isNotEmpty()) { 25 | when (val node = nodeDeque.pop()) { 26 | is SealedClassNode.Object -> sealedObjects.add(node.className) 27 | is SealedClassNode.SealedClass -> 28 | when (treeTraversalOrder) { 29 | TreeTraversalOrder.PRE_ORDER -> { 30 | // We push any sealed classes first, so that they come after the sealed classes in the stack 31 | // We reverse each set so that the original ordering is preserved within each category 32 | node.sealedSubclasses.filterIsInstance() 33 | .reversed() 34 | .forEach(nodeDeque::push) 35 | node.sealedSubclasses.filterIsInstance() 36 | .reversed() 37 | .forEach(nodeDeque::push) 38 | } 39 | TreeTraversalOrder.IN_ORDER -> { 40 | // For in order, we don't change the order of the children, apart from reversing our insertion 41 | // order so that the first child ends up at the top of the stack. 42 | node.sealedSubclasses.reversed().forEach(nodeDeque::push) 43 | } 44 | TreeTraversalOrder.POST_ORDER -> { 45 | // We push any leaf objects first, so that they come after the sealed classes in the stack 46 | // We reverse each set so that the original ordering is preserved within each category 47 | node.sealedSubclasses.filterIsInstance() 48 | .reversed() 49 | .forEach(nodeDeque::push) 50 | node.sealedSubclasses.filterIsInstance() 51 | .reversed() 52 | .forEach(nodeDeque::push) 53 | } 54 | TreeTraversalOrder.LEVEL_ORDER -> { 55 | // For level order, we process children as a queue 56 | node.sealedSubclasses.forEach(nodeDeque::addLast) 57 | } 58 | } 59 | } 60 | } 61 | 62 | return sealedObjects.distinct() 63 | } 64 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/Unique.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common 2 | 3 | /** 4 | * Returns true if and only if all elements of the [Array] are unique based on the key computed by the [selector]. 5 | * 6 | * This is logically equivalent to checking if [Array.distinctBy] made the array smaller, but is more efficient 7 | * due to short-circuiting. 8 | */ 9 | public fun Array.areUniqueBy(selector: (T) -> K): Boolean { 10 | val set = mutableSetOf() 11 | for (e in this) { 12 | if (!set.add(selector(e))) { 13 | return false 14 | } 15 | } 16 | return true 17 | } 18 | 19 | /** 20 | * Returns true if and only if all elements of the [Iterable] are unique based on the key computed by the [selector]. 21 | * 22 | * This is logically equivalent to checking if [Iterable.distinctBy] made the array smaller, but is more efficient 23 | * due to short-circuiting. 24 | */ 25 | public fun Iterable.areUniqueBy(selector: (T) -> K): Boolean { 26 | val set = mutableSetOf() 27 | for (e in this) { 28 | if (!set.add(selector(e))) { 29 | return false 30 | } 31 | } 32 | return true 33 | } 34 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/Visibility.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | 5 | /** 6 | * A restricted enumeration of visibilities for use with sealed classes. Due to generating code that is in a different 7 | * file, we can only reasonably work with sealed classes and companion objects that are public or internal. 8 | */ 9 | public enum class Visibility(public val kModifier: KModifier) { 10 | PUBLIC(KModifier.PUBLIC), 11 | INTERNAL(KModifier.INTERNAL); 12 | } 13 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/CamelCase.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | /** 4 | * Converts a PascalCase string to a camelCase string. That is, this function lowercases the first letter. 5 | * 6 | * This function assumes that [pascalCase] is non-empty. 7 | */ 8 | internal fun pascalCaseToCamelCase(pascalCase: String): String = 9 | pascalCase.take(1).lowercase() + pascalCase.drop(1) 10 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/EnumForSealedEnumTypeSpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.CodeBlock 6 | import com.squareup.kotlinpoet.FunSpec 7 | import com.squareup.kotlinpoet.TypeName 8 | import com.squareup.kotlinpoet.TypeSpec 9 | import javax.lang.model.element.TypeElement 10 | 11 | /** 12 | * A builder for an enum class for the given [sealedClass]. 13 | * 14 | * Given the [ClassName] of the [sealedClass], the [TypeElement] for the [sealedClassCompanionObjectElement], and 15 | * the list of [ClassName]s for the sealed subclasses, [build] will generate a [TypeSpec] for an enum class. 16 | * 17 | * The name of the created enum class will be the name of the sealed class concatenated with [enumPrefix] concatenated 18 | * with "Enum". 19 | */ 20 | internal data class EnumForSealedEnumTypeSpec( 21 | private val sealedClass: SealedClass, 22 | private val sealedClassVisibility: Visibility, 23 | private val sealedClassCompanionObjectElement: TypeElement?, 24 | private val sealedObjects: List, 25 | private val parameterizedSealedClass: TypeName, 26 | private val enumPrefix: String, 27 | private val sealedClassInterfaces: List 28 | ) { 29 | private val typeSpecBuilder = TypeSpec.enumBuilder(sealedClass.createEnumForSealedEnumName(enumPrefix)) 30 | .addModifiers(sealedClassVisibility.kModifier) 31 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 32 | .addKdoc("An isomorphic enum for the sealed class [%T]", sealedClass) 33 | .primaryConstructor( 34 | FunSpec.constructorBuilder() 35 | .apply { 36 | if (sealedClassInterfaces.isNotEmpty()) { 37 | // Only add the sealed object constructor parameter if it is needed to implement an interface 38 | addParameter("sealedObject", parameterizedSealedClass) 39 | } 40 | } 41 | .build() 42 | ) 43 | .apply { 44 | sealedClassInterfaces.forEach { 45 | superinterfaces[it] = CodeBlock.of("sealedObject") 46 | } 47 | sealedObjects.forEach { 48 | addEnumConstant( 49 | it.createValueName(), 50 | TypeSpec.anonymousClassBuilder() 51 | .apply { 52 | if (sealedClassInterfaces.isNotEmpty()) { 53 | // Only add the sealed object constructor parameter if it is needed to implement an 54 | // interface 55 | addSuperclassConstructorParameter(CodeBlock.of(it.canonicalName)) 56 | } 57 | } 58 | .build() 59 | ) 60 | } 61 | } 62 | 63 | fun build(): TypeSpec = typeSpecBuilder.build() 64 | } 65 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/EnumSealedObjectPropertySpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import com.squareup.kotlinpoet.TypeName 8 | import javax.lang.model.element.TypeElement 9 | 10 | internal data class EnumSealedObjectPropertySpec( 11 | private val sealedClass: SealedClass, 12 | private val sealedClassVisibility: Visibility, 13 | private val parameterizedSealedClass: TypeName, 14 | private val sealedClassCompanionObjectElement: TypeElement?, 15 | private val sealedEnum: ClassName, 16 | private val enumForSealedEnum: ClassName 17 | ) { 18 | fun build(): PropertySpec { 19 | val propertySpecBuilder = PropertySpec.builder("sealedObject", parameterizedSealedClass) 20 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 21 | .addKdoc("The isomorphic [%T] for [this].", sealedClass) 22 | .receiver(enumForSealedEnum) 23 | .addModifiers(sealedClassVisibility.kModifier) 24 | .getter( 25 | FunSpec.getterBuilder() 26 | .addStatement("return %T.enumToSealedObject(this)", sealedEnum) 27 | .build() 28 | ) 29 | 30 | return propertySpecBuilder.build() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/MaybeAddOriginatingElement.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.squareup.kotlinpoet.OriginatingElementsHolder 4 | import javax.lang.model.element.Element 5 | 6 | /** 7 | * Adds [originatingElement] as an originating element if it is non-null. 8 | */ 9 | internal fun > T.maybeAddOriginatingElement(originatingElement: Element?): T = 10 | originatingElement?.let(::addOriginatingElement) ?: this 11 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedClassExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.SealedEnum 4 | import com.squareup.kotlinpoet.ClassName 5 | 6 | internal typealias SealedClass = ClassName 7 | 8 | internal typealias SealedObject = ClassName 9 | 10 | /** 11 | * Creates the name of the [SealedEnum] for the given [SealedClass] and [enumPrefix]. 12 | * 13 | * Ex: `Outer.Inner` with an [enumPrefix] of `LevelOrder` produces `"Outer_InnerLevelOrderSealedEnum"` 14 | */ 15 | internal fun SealedClass.createSealedEnumName(enumPrefix: String): String = 16 | "${simpleNames.joinToString("_")}${enumPrefix}SealedEnum" 17 | 18 | /** 19 | * Creates the name of the enum for the given [SealedClass] and [enumPrefix]. 20 | * 21 | * Ex: `Outer.Inner` with an [enumPrefix] of `LevelOrder` produces `"Outer_InnerLevelOrderEnum"` 22 | */ 23 | internal fun SealedClass.createEnumForSealedEnumName(enumPrefix: String): String = 24 | "${simpleNames.joinToString("_")}${enumPrefix}Enum" 25 | 26 | /** 27 | * Creates the name of an enum value for the given object which is a subclass of a [SealedEnum]. 28 | * 29 | * Ex: `Outer.Inner.First` becomes `"Outer_Inner_First"` 30 | */ 31 | internal fun SealedObject.createValueName(): String = 32 | simpleNames.joinToString("_") 33 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumEnumPropertySpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import com.squareup.kotlinpoet.TypeName 8 | import javax.lang.model.element.TypeElement 9 | 10 | internal data class SealedEnumEnumPropertySpec( 11 | private val sealedClassVisibility: Visibility, 12 | private val parameterizedSealedClass: TypeName, 13 | private val sealedClassCompanionObjectElement: TypeElement?, 14 | private val sealedEnum: ClassName, 15 | private val enumForSealedEnum: ClassName, 16 | private val enumPrefix: String 17 | ) { 18 | fun build(): PropertySpec { 19 | val propertySpecBuilder = PropertySpec.builder(pascalCaseToCamelCase(enumPrefix + "Enum"), enumForSealedEnum) 20 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 21 | .addKdoc("The isomorphic [%T] for [this].", enumForSealedEnum) 22 | .receiver(parameterizedSealedClass) 23 | .addModifiers(sealedClassVisibility.kModifier) 24 | .getter( 25 | FunSpec.getterBuilder() 26 | .addStatement("return %T.sealedObjectToEnum(this)", sealedEnum) 27 | .build() 28 | ) 29 | 30 | return propertySpecBuilder.build() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumNamePropertySpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import com.squareup.kotlinpoet.TypeName 8 | import javax.lang.model.element.TypeElement 9 | 10 | internal data class SealedEnumNamePropertySpec( 11 | private val sealedClassVisibility: Visibility, 12 | private val parameterizedSealedClass: TypeName, 13 | private val sealedClassCompanionObjectElement: TypeElement?, 14 | private val sealedEnum: ClassName, 15 | private val enumPrefix: String 16 | ) { 17 | fun build(): PropertySpec { 18 | val propertySpecBuilder = PropertySpec.builder(pascalCaseToCamelCase(enumPrefix + "Name"), String::class) 19 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 20 | .addKdoc("The name of [this] for use with valueOf.") 21 | .receiver(parameterizedSealedClass) 22 | .addModifiers(sealedClassVisibility.kModifier) 23 | .getter( 24 | FunSpec.getterBuilder() 25 | .addStatement("return %T.nameOf(this)", sealedEnum) 26 | .build() 27 | ) 28 | 29 | return propertySpecBuilder.build() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumOrdinalPropertySpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import com.squareup.kotlinpoet.TypeName 8 | import javax.lang.model.element.TypeElement 9 | 10 | internal data class SealedEnumOrdinalPropertySpec( 11 | private val sealedClassVisibility: Visibility, 12 | private val parameterizedSealedClass: TypeName, 13 | private val sealedClassCompanionObjectElement: TypeElement?, 14 | private val sealedEnum: ClassName, 15 | private val enumPrefix: String 16 | ) { 17 | fun build(): PropertySpec { 18 | val propertySpecBuilder = PropertySpec.builder(pascalCaseToCamelCase(enumPrefix + "Ordinal"), Int::class) 19 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 20 | .addKdoc("The index of [this] in the values list.") 21 | .receiver(parameterizedSealedClass) 22 | .addModifiers(sealedClassVisibility.kModifier) 23 | .getter( 24 | FunSpec.getterBuilder() 25 | .addStatement("return %T.ordinalOf(this)", sealedEnum) 26 | .build() 27 | ) 28 | 29 | return propertySpecBuilder.build() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumSealedEnumPropertySpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.SealedEnum 4 | import com.livefront.sealedenum.internal.common.Visibility 5 | import com.squareup.kotlinpoet.ClassName 6 | import com.squareup.kotlinpoet.FunSpec 7 | import com.squareup.kotlinpoet.PropertySpec 8 | import javax.lang.model.element.TypeElement 9 | 10 | internal data class SealedEnumSealedEnumPropertySpec( 11 | private val sealedClass: SealedClass, 12 | private val sealedClassCompanionObject: ClassName, 13 | private val sealedClassCompanionObjectEffectiveVisibility: Visibility, 14 | private val sealedClassCompanionObjectElement: TypeElement?, 15 | private val sealedEnum: ClassName, 16 | private val enumPrefix: String 17 | ) { 18 | fun build(): PropertySpec { 19 | val propertySpecBuilder = PropertySpec.builder(pascalCaseToCamelCase(enumPrefix + "SealedEnum"), sealedEnum) 20 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 21 | .addKdoc("Returns an implementation of [%T] for the sealed class [%T]", SealedEnum::class, sealedClass) 22 | .receiver(sealedClassCompanionObject) 23 | .addModifiers(sealedClassCompanionObjectEffectiveVisibility.kModifier) 24 | .getter( 25 | FunSpec.getterBuilder() 26 | .addStatement("return %T", sealedEnum) 27 | .build() 28 | ) 29 | 30 | return propertySpecBuilder.build() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumValueOfFunSpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.TypeName 7 | import javax.lang.model.element.TypeElement 8 | 9 | internal data class SealedEnumValueOfFunSpec( 10 | private val sealedClass: SealedClass, 11 | private val parameterizedSealedClass: TypeName, 12 | private val sealedClassCompanionObject: ClassName, 13 | private val sealedClassCompanionObjectEffectiveVisibility: Visibility, 14 | private val sealedClassCompanionObjectElement: TypeElement?, 15 | private val sealedEnum: ClassName, 16 | private val enumPrefix: String 17 | ) { 18 | fun build(): FunSpec { 19 | val funSpecBuilder = FunSpec.builder(pascalCaseToCamelCase(enumPrefix + "ValueOf")) 20 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 21 | .addKdoc( 22 | """ 23 | Returns the [%T] object for the given [name]. 24 | 25 | If the given name doesn't correspond to any [%T], an [IllegalArgumentException] will be thrown. 26 | """.trimIndent(), 27 | sealedClass, 28 | sealedClass 29 | ) 30 | .receiver(sealedClassCompanionObject) 31 | .addModifiers(sealedClassCompanionObjectEffectiveVisibility.kModifier) 32 | .returns(parameterizedSealedClass) 33 | .addParameter("name", String::class) 34 | .addStatement("return %T.valueOf(name)", sealedEnum) 35 | 36 | return funSpecBuilder.build() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /processing-common/src/main/kotlin/com/livefront/sealedenum/internal/common/spec/SealedEnumValuesPropertySpec.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.common.spec 2 | 3 | import com.livefront.sealedenum.internal.common.Visibility 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 7 | import com.squareup.kotlinpoet.PropertySpec 8 | import com.squareup.kotlinpoet.TypeName 9 | import com.squareup.kotlinpoet.asClassName 10 | import javax.lang.model.element.TypeElement 11 | 12 | internal data class SealedEnumValuesPropertySpec( 13 | private val sealedClass: SealedClass, 14 | private val parameterizedSealedClass: TypeName, 15 | private val sealedClassCompanionObject: ClassName, 16 | private val sealedClassCompanionObjectEffectiveVisibility: Visibility, 17 | private val sealedClassCompanionObjectElement: TypeElement?, 18 | private val sealedEnum: ClassName, 19 | private val enumPrefix: String 20 | ) { 21 | private val listOfSealedClass = List::class.asClassName().parameterizedBy(parameterizedSealedClass) 22 | 23 | fun build(): PropertySpec { 24 | val propertySpecBuilder = PropertySpec.builder(pascalCaseToCamelCase(enumPrefix + "Values"), listOfSealedClass) 25 | .maybeAddOriginatingElement(sealedClassCompanionObjectElement) 26 | .addKdoc("A list of all [%T] objects.", sealedClass) 27 | .receiver(sealedClassCompanionObject) 28 | .addModifiers(sealedClassCompanionObjectEffectiveVisibility.kModifier) 29 | .getter( 30 | FunSpec.getterBuilder() 31 | .addStatement("return %T.values", sealedEnum) 32 | .build() 33 | ) 34 | 35 | return propertySpecBuilder.build() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /processing-tests/common/test/java/com/livefront/sealedenum/compilation/visibility/JavaPrivateInterfaceOuterClass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility; 2 | 3 | public class JavaPrivateInterfaceOuterClass { 4 | 5 | private interface PrivateInterface { 6 | 7 | } 8 | 9 | public class JavaPrivateInterfaceBaseClass implements PrivateInterface { 10 | 11 | } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /processing-tests/common/test/java/com/livefront/sealedenum/compilation/visibility/JavaPrivateInterfaceSubclass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility; 2 | 3 | public class JavaPrivateInterfaceSubclass extends JavaPrivateInterfaceOuterClass.JavaPrivateInterfaceBaseClass { 4 | public JavaPrivateInterfaceSubclass(JavaPrivateInterfaceOuterClass outer) { 5 | outer.super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /processing-tests/common/test/java/com/livefront/sealedenum/compilation/visibility/JavaProtectedInterfaceBaseClass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility; 2 | 3 | public class JavaProtectedInterfaceBaseClass { 4 | protected interface ProtectedInterface { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /processing-tests/common/test/java/com/livefront/sealedenum/compilation/visibility/JavaProtectedInterfaceSubclass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility; 2 | 3 | public class JavaProtectedInterfaceSubclass 4 | extends JavaProtectedInterfaceBaseClass 5 | implements JavaProtectedInterfaceBaseClass.ProtectedInterface { 6 | } 7 | -------------------------------------------------------------------------------- /processing-tests/common/test/java/com/livefront/sealedenum/compilation/visibility/subpackage/JavaProtectedInterfaceBaseClass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility.subpackage; 2 | 3 | public class JavaProtectedInterfaceBaseClass { 4 | protected interface ProtectedInterface { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /processing-tests/common/test/java/com/livefront/sealedenum/compilation/visibility/subpackage/JavaProtectedInterfaceSubclass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility.subpackage; 2 | 3 | public class JavaProtectedInterfaceSubclass 4 | extends JavaProtectedInterfaceBaseClass 5 | implements JavaProtectedInterfaceBaseClass.ProtectedInterface { 6 | } 7 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/EmptySealedClass.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed class EmptySealedClass { 7 | @GenSealedEnum(generateEnum = true) 8 | companion object 9 | } 10 | 11 | @Language("kotlin") 12 | val emptySealedClassGenerated = """ 13 | package com.livefront.sealedenum.compilation.basic 14 | 15 | import com.livefront.sealedenum.EnumForSealedEnumProvider 16 | import com.livefront.sealedenum.SealedEnum 17 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 18 | import kotlin.Int 19 | import kotlin.LazyThreadSafetyMode 20 | import kotlin.String 21 | import kotlin.collections.List 22 | import kotlin.reflect.KClass 23 | 24 | /** 25 | * An isomorphic enum for the sealed class [EmptySealedClass] 26 | */ 27 | public enum class EmptySealedClassEnum() 28 | 29 | /** 30 | * The isomorphic [EmptySealedClassEnum] for [this]. 31 | */ 32 | public val EmptySealedClass.`enum`: EmptySealedClassEnum 33 | get() = EmptySealedClassSealedEnum.sealedObjectToEnum(this) 34 | 35 | /** 36 | * The isomorphic [EmptySealedClass] for [this]. 37 | */ 38 | public val EmptySealedClassEnum.sealedObject: EmptySealedClass 39 | get() = EmptySealedClassSealedEnum.enumToSealedObject(this) 40 | 41 | /** 42 | * An implementation of [SealedEnum] for the sealed class [EmptySealedClass] 43 | */ 44 | public object EmptySealedClassSealedEnum : SealedEnum, 45 | SealedEnumWithEnumProvider, 46 | EnumForSealedEnumProvider { 47 | public override val values: List by lazy(mode = 48 | LazyThreadSafetyMode.PUBLICATION) { 49 | emptyList() 50 | } 51 | 52 | 53 | public override val enumClass: KClass 54 | get() = EmptySealedClassEnum::class 55 | 56 | public override fun ordinalOf(obj: EmptySealedClass): Int = throw 57 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 58 | 59 | public override fun nameOf(obj: EmptySealedClass): String = throw 60 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 61 | 62 | public override fun valueOf(name: String): EmptySealedClass = throw 63 | IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 64 | 65 | public override fun sealedObjectToEnum(obj: EmptySealedClass): EmptySealedClassEnum = throw 66 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 67 | 68 | public override fun enumToSealedObject(`enum`: EmptySealedClassEnum): EmptySealedClass = throw 69 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 70 | } 71 | 72 | /** 73 | * The index of [this] in the values list. 74 | */ 75 | public val EmptySealedClass.ordinal: Int 76 | get() = EmptySealedClassSealedEnum.ordinalOf(this) 77 | 78 | /** 79 | * The name of [this] for use with valueOf. 80 | */ 81 | public val EmptySealedClass.name: String 82 | get() = EmptySealedClassSealedEnum.nameOf(this) 83 | 84 | /** 85 | * A list of all [EmptySealedClass] objects. 86 | */ 87 | public val EmptySealedClass.Companion.values: List 88 | get() = EmptySealedClassSealedEnum.values 89 | 90 | /** 91 | * Returns an implementation of [SealedEnum] for the sealed class [EmptySealedClass] 92 | */ 93 | public val EmptySealedClass.Companion.sealedEnum: EmptySealedClassSealedEnum 94 | get() = EmptySealedClassSealedEnum 95 | 96 | /** 97 | * Returns the [EmptySealedClass] object for the given [name]. 98 | * 99 | * If the given name doesn't correspond to any [EmptySealedClass], an [IllegalArgumentException] 100 | * will be thrown. 101 | */ 102 | public fun EmptySealedClass.Companion.valueOf(name: String): EmptySealedClass = 103 | EmptySealedClassSealedEnum.valueOf(name) 104 | 105 | """.trimIndent() 106 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/EmptySealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class EmptySealedClassTests { 11 | @Test 12 | fun `empty sealed class`() { 13 | assertEquals(emptyList(), EmptySealedClass.values) 14 | } 15 | 16 | @Test 17 | fun `empty enum for sealed class`() { 18 | assertEquals( 19 | EmptySealedClass.values.map(EmptySealedClass::enum), 20 | enumValues().toList() 21 | ) 22 | } 23 | 24 | @Test 25 | fun `correct enum class`() { 26 | assertEquals(EmptySealedClassEnum::class, EmptySealedClass.sealedEnum.enumClass) 27 | } 28 | 29 | @Test 30 | fun `compilation generates correct code`() { 31 | val result = compile(getCommonSourceFile("compilation", "basic", "EmptySealedClass.kt")) 32 | 33 | assertCompiles(result) 34 | assertGeneratedFileMatches("EmptySealedClass_SealedEnum.kt", emptySealedClassGenerated, result) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/EmptySealedInterface.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed interface EmptySealedInterface { 7 | @GenSealedEnum(generateEnum = true) 8 | companion object 9 | } 10 | 11 | @Language("kotlin") 12 | val emptySealedInterfaceGenerated = """ 13 | package com.livefront.sealedenum.compilation.basic 14 | 15 | import com.livefront.sealedenum.EnumForSealedEnumProvider 16 | import com.livefront.sealedenum.SealedEnum 17 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 18 | import kotlin.Int 19 | import kotlin.LazyThreadSafetyMode 20 | import kotlin.String 21 | import kotlin.collections.List 22 | import kotlin.reflect.KClass 23 | 24 | /** 25 | * An isomorphic enum for the sealed class [EmptySealedInterface] 26 | */ 27 | public enum class EmptySealedInterfaceEnum() 28 | 29 | /** 30 | * The isomorphic [EmptySealedInterfaceEnum] for [this]. 31 | */ 32 | public val EmptySealedInterface.`enum`: EmptySealedInterfaceEnum 33 | get() = EmptySealedInterfaceSealedEnum.sealedObjectToEnum(this) 34 | 35 | /** 36 | * The isomorphic [EmptySealedInterface] for [this]. 37 | */ 38 | public val EmptySealedInterfaceEnum.sealedObject: EmptySealedInterface 39 | get() = EmptySealedInterfaceSealedEnum.enumToSealedObject(this) 40 | 41 | /** 42 | * An implementation of [SealedEnum] for the sealed class [EmptySealedInterface] 43 | */ 44 | public object EmptySealedInterfaceSealedEnum : SealedEnum, 45 | SealedEnumWithEnumProvider, 46 | EnumForSealedEnumProvider { 47 | public override val values: List by lazy(mode = 48 | LazyThreadSafetyMode.PUBLICATION) { 49 | emptyList() 50 | } 51 | 52 | 53 | public override val enumClass: KClass 54 | get() = EmptySealedInterfaceEnum::class 55 | 56 | public override fun ordinalOf(obj: EmptySealedInterface): Int = throw 57 | AssertionError("Constructing a EmptySealedInterface is impossible, since it has no sealed subclasses") 58 | 59 | public override fun nameOf(obj: EmptySealedInterface): String = throw 60 | AssertionError("Constructing a EmptySealedInterface is impossible, since it has no sealed subclasses") 61 | 62 | public override fun valueOf(name: String): EmptySealedInterface = throw 63 | IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 64 | 65 | public override fun sealedObjectToEnum(obj: EmptySealedInterface): EmptySealedInterfaceEnum = 66 | throw 67 | AssertionError("Constructing a EmptySealedInterface is impossible, since it has no sealed subclasses") 68 | 69 | public override fun enumToSealedObject(`enum`: EmptySealedInterfaceEnum): EmptySealedInterface = 70 | throw 71 | AssertionError("Constructing a EmptySealedInterface is impossible, since it has no sealed subclasses") 72 | } 73 | 74 | /** 75 | * The index of [this] in the values list. 76 | */ 77 | public val EmptySealedInterface.ordinal: Int 78 | get() = EmptySealedInterfaceSealedEnum.ordinalOf(this) 79 | 80 | /** 81 | * The name of [this] for use with valueOf. 82 | */ 83 | public val EmptySealedInterface.name: String 84 | get() = EmptySealedInterfaceSealedEnum.nameOf(this) 85 | 86 | /** 87 | * A list of all [EmptySealedInterface] objects. 88 | */ 89 | public val EmptySealedInterface.Companion.values: List 90 | get() = EmptySealedInterfaceSealedEnum.values 91 | 92 | /** 93 | * Returns an implementation of [SealedEnum] for the sealed class [EmptySealedInterface] 94 | */ 95 | public val EmptySealedInterface.Companion.sealedEnum: EmptySealedInterfaceSealedEnum 96 | get() = EmptySealedInterfaceSealedEnum 97 | 98 | /** 99 | * Returns the [EmptySealedInterface] object for the given [name]. 100 | * 101 | * If the given name doesn't correspond to any [EmptySealedInterface], an [IllegalArgumentException] 102 | * will be thrown. 103 | */ 104 | public fun EmptySealedInterface.Companion.valueOf(name: String): EmptySealedInterface = 105 | EmptySealedInterfaceSealedEnum.valueOf(name) 106 | 107 | """.trimIndent() 108 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/EmptySealedInterfaceTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class EmptySealedInterfaceTests { 11 | @Test 12 | fun `empty sealed interface`() { 13 | assertEquals(emptyList(), EmptySealedInterface.values) 14 | } 15 | 16 | @Test 17 | fun `empty enum for sealed interface`() { 18 | assertEquals( 19 | EmptySealedInterface.values.map(EmptySealedInterface::enum), 20 | enumValues().toList() 21 | ) 22 | } 23 | 24 | @Test 25 | fun `correct enum class`() { 26 | assertEquals(EmptySealedInterfaceEnum::class, EmptySealedInterface.sealedEnum.enumClass) 27 | } 28 | 29 | @Test 30 | fun `compilation generates correct code`() { 31 | val result = compile(getCommonSourceFile("compilation", "basic", "EmptySealedInterface.kt")) 32 | 33 | assertCompiles(result) 34 | assertGeneratedFileMatches("EmptySealedInterface_SealedEnum.kt", emptySealedInterfaceGenerated, result) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/OneObjectSealedClass.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed class OneObjectSealedClass { 7 | object FirstObject : OneObjectSealedClass() 8 | 9 | @GenSealedEnum(generateEnum = true) 10 | companion object 11 | } 12 | 13 | @Language("kotlin") 14 | val oneObjectSealedClassGenerated = """ 15 | package com.livefront.sealedenum.compilation.basic 16 | 17 | import com.livefront.sealedenum.EnumForSealedEnumProvider 18 | import com.livefront.sealedenum.SealedEnum 19 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 20 | import kotlin.Int 21 | import kotlin.LazyThreadSafetyMode 22 | import kotlin.String 23 | import kotlin.collections.List 24 | import kotlin.reflect.KClass 25 | 26 | /** 27 | * An isomorphic enum for the sealed class [OneObjectSealedClass] 28 | */ 29 | public enum class OneObjectSealedClassEnum() { 30 | OneObjectSealedClass_FirstObject, 31 | } 32 | 33 | /** 34 | * The isomorphic [OneObjectSealedClassEnum] for [this]. 35 | */ 36 | public val OneObjectSealedClass.`enum`: OneObjectSealedClassEnum 37 | get() = OneObjectSealedClassSealedEnum.sealedObjectToEnum(this) 38 | 39 | /** 40 | * The isomorphic [OneObjectSealedClass] for [this]. 41 | */ 42 | public val OneObjectSealedClassEnum.sealedObject: OneObjectSealedClass 43 | get() = OneObjectSealedClassSealedEnum.enumToSealedObject(this) 44 | 45 | /** 46 | * An implementation of [SealedEnum] for the sealed class [OneObjectSealedClass] 47 | */ 48 | public object OneObjectSealedClassSealedEnum : SealedEnum, 49 | SealedEnumWithEnumProvider, 50 | EnumForSealedEnumProvider { 51 | public override val values: List by lazy(mode = 52 | LazyThreadSafetyMode.PUBLICATION) { 53 | listOf( 54 | OneObjectSealedClass.FirstObject 55 | ) 56 | } 57 | 58 | 59 | public override val enumClass: KClass 60 | get() = OneObjectSealedClassEnum::class 61 | 62 | public override fun ordinalOf(obj: OneObjectSealedClass): Int = when (obj) { 63 | is OneObjectSealedClass.FirstObject -> 0 64 | } 65 | 66 | public override fun nameOf(obj: OneObjectSealedClass): String = when (obj) { 67 | is OneObjectSealedClass.FirstObject -> "OneObjectSealedClass_FirstObject" 68 | } 69 | 70 | public override fun valueOf(name: String): OneObjectSealedClass = when (name) { 71 | "OneObjectSealedClass_FirstObject" -> OneObjectSealedClass.FirstObject 72 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 73 | } 74 | 75 | public override fun sealedObjectToEnum(obj: OneObjectSealedClass): OneObjectSealedClassEnum = 76 | when (obj) { 77 | is OneObjectSealedClass.FirstObject -> 78 | OneObjectSealedClassEnum.OneObjectSealedClass_FirstObject 79 | } 80 | 81 | public override fun enumToSealedObject(`enum`: OneObjectSealedClassEnum): OneObjectSealedClass = 82 | when (enum) { 83 | OneObjectSealedClassEnum.OneObjectSealedClass_FirstObject -> 84 | OneObjectSealedClass.FirstObject 85 | } 86 | } 87 | 88 | /** 89 | * The index of [this] in the values list. 90 | */ 91 | public val OneObjectSealedClass.ordinal: Int 92 | get() = OneObjectSealedClassSealedEnum.ordinalOf(this) 93 | 94 | /** 95 | * The name of [this] for use with valueOf. 96 | */ 97 | public val OneObjectSealedClass.name: String 98 | get() = OneObjectSealedClassSealedEnum.nameOf(this) 99 | 100 | /** 101 | * A list of all [OneObjectSealedClass] objects. 102 | */ 103 | public val OneObjectSealedClass.Companion.values: List 104 | get() = OneObjectSealedClassSealedEnum.values 105 | 106 | /** 107 | * Returns an implementation of [SealedEnum] for the sealed class [OneObjectSealedClass] 108 | */ 109 | public val OneObjectSealedClass.Companion.sealedEnum: OneObjectSealedClassSealedEnum 110 | get() = OneObjectSealedClassSealedEnum 111 | 112 | /** 113 | * Returns the [OneObjectSealedClass] object for the given [name]. 114 | * 115 | * If the given name doesn't correspond to any [OneObjectSealedClass], an [IllegalArgumentException] 116 | * will be thrown. 117 | */ 118 | public fun OneObjectSealedClass.Companion.valueOf(name: String): OneObjectSealedClass = 119 | OneObjectSealedClassSealedEnum.valueOf(name) 120 | 121 | """.trimIndent() 122 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/OneObjectSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class OneObjectSealedClassTests { 11 | @Test 12 | fun `one object sealed class`() { 13 | assertEquals(listOf(OneObjectSealedClass.FirstObject), OneObjectSealedClass.values) 14 | } 15 | 16 | @Test 17 | fun `one enum for sealed class`() { 18 | assertEquals( 19 | listOf(OneObjectSealedClassEnum.OneObjectSealedClass_FirstObject), 20 | enumValues().toList() 21 | ) 22 | } 23 | 24 | @Test 25 | fun `one enum for sealed class with mapping`() { 26 | assertEquals( 27 | OneObjectSealedClass.values.map(OneObjectSealedClass::enum), 28 | enumValues().toList() 29 | ) 30 | } 31 | 32 | @Test 33 | fun `correct enum class`() { 34 | assertEquals(OneObjectSealedClassEnum::class, OneObjectSealedClass.sealedEnum.enumClass) 35 | } 36 | 37 | @Test 38 | fun `compilation generates correct code`() { 39 | val result = compile(getCommonSourceFile("compilation", "basic", "OneObjectSealedClass.kt")) 40 | 41 | assertCompiles(result) 42 | assertGeneratedFileMatches("OneObjectSealedClass_SealedEnum.kt", oneObjectSealedClassGenerated, result) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/OneObjectSealedInterface.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed interface OneObjectSealedInterface { 7 | object FirstObject : OneObjectSealedInterface 8 | 9 | @GenSealedEnum(generateEnum = true) 10 | companion object 11 | } 12 | 13 | @Language("kotlin") 14 | val oneObjectSealedInterfaceGenerated = """ 15 | package com.livefront.sealedenum.compilation.basic 16 | 17 | import com.livefront.sealedenum.EnumForSealedEnumProvider 18 | import com.livefront.sealedenum.SealedEnum 19 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 20 | import kotlin.Int 21 | import kotlin.LazyThreadSafetyMode 22 | import kotlin.String 23 | import kotlin.collections.List 24 | import kotlin.reflect.KClass 25 | 26 | /** 27 | * An isomorphic enum for the sealed class [OneObjectSealedInterface] 28 | */ 29 | public enum class OneObjectSealedInterfaceEnum() { 30 | OneObjectSealedInterface_FirstObject, 31 | } 32 | 33 | /** 34 | * The isomorphic [OneObjectSealedInterfaceEnum] for [this]. 35 | */ 36 | public val OneObjectSealedInterface.`enum`: OneObjectSealedInterfaceEnum 37 | get() = OneObjectSealedInterfaceSealedEnum.sealedObjectToEnum(this) 38 | 39 | /** 40 | * The isomorphic [OneObjectSealedInterface] for [this]. 41 | */ 42 | public val OneObjectSealedInterfaceEnum.sealedObject: OneObjectSealedInterface 43 | get() = OneObjectSealedInterfaceSealedEnum.enumToSealedObject(this) 44 | 45 | /** 46 | * An implementation of [SealedEnum] for the sealed class [OneObjectSealedInterface] 47 | */ 48 | public object OneObjectSealedInterfaceSealedEnum : SealedEnum, 49 | SealedEnumWithEnumProvider, 50 | EnumForSealedEnumProvider { 51 | public override val values: List by lazy(mode = 52 | LazyThreadSafetyMode.PUBLICATION) { 53 | listOf( 54 | OneObjectSealedInterface.FirstObject 55 | ) 56 | } 57 | 58 | 59 | public override val enumClass: KClass 60 | get() = OneObjectSealedInterfaceEnum::class 61 | 62 | public override fun ordinalOf(obj: OneObjectSealedInterface): Int = when (obj) { 63 | is OneObjectSealedInterface.FirstObject -> 0 64 | } 65 | 66 | public override fun nameOf(obj: OneObjectSealedInterface): String = when (obj) { 67 | is OneObjectSealedInterface.FirstObject -> "OneObjectSealedInterface_FirstObject" 68 | } 69 | 70 | public override fun valueOf(name: String): OneObjectSealedInterface = when (name) { 71 | "OneObjectSealedInterface_FirstObject" -> OneObjectSealedInterface.FirstObject 72 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 73 | } 74 | 75 | public override fun sealedObjectToEnum(obj: OneObjectSealedInterface): 76 | OneObjectSealedInterfaceEnum = when (obj) { 77 | is OneObjectSealedInterface.FirstObject -> 78 | OneObjectSealedInterfaceEnum.OneObjectSealedInterface_FirstObject 79 | } 80 | 81 | public override fun enumToSealedObject(`enum`: OneObjectSealedInterfaceEnum): 82 | OneObjectSealedInterface = when (enum) { 83 | OneObjectSealedInterfaceEnum.OneObjectSealedInterface_FirstObject -> 84 | OneObjectSealedInterface.FirstObject 85 | } 86 | } 87 | 88 | /** 89 | * The index of [this] in the values list. 90 | */ 91 | public val OneObjectSealedInterface.ordinal: Int 92 | get() = OneObjectSealedInterfaceSealedEnum.ordinalOf(this) 93 | 94 | /** 95 | * The name of [this] for use with valueOf. 96 | */ 97 | public val OneObjectSealedInterface.name: String 98 | get() = OneObjectSealedInterfaceSealedEnum.nameOf(this) 99 | 100 | /** 101 | * A list of all [OneObjectSealedInterface] objects. 102 | */ 103 | public val OneObjectSealedInterface.Companion.values: List 104 | get() = OneObjectSealedInterfaceSealedEnum.values 105 | 106 | /** 107 | * Returns an implementation of [SealedEnum] for the sealed class [OneObjectSealedInterface] 108 | */ 109 | public val OneObjectSealedInterface.Companion.sealedEnum: OneObjectSealedInterfaceSealedEnum 110 | get() = OneObjectSealedInterfaceSealedEnum 111 | 112 | /** 113 | * Returns the [OneObjectSealedInterface] object for the given [name]. 114 | * 115 | * If the given name doesn't correspond to any [OneObjectSealedInterface], an 116 | * [IllegalArgumentException] will be thrown. 117 | */ 118 | public fun OneObjectSealedInterface.Companion.valueOf(name: String): OneObjectSealedInterface = 119 | OneObjectSealedInterfaceSealedEnum.valueOf(name) 120 | 121 | """.trimIndent() 122 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/OneObjectSealedInterfaceTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class OneObjectSealedInterfaceTests { 11 | @Test 12 | fun `one object sealed interface`() { 13 | assertEquals(listOf(OneObjectSealedInterface.FirstObject), OneObjectSealedInterface.values) 14 | } 15 | 16 | @Test 17 | fun `one enum for sealed interface`() { 18 | assertEquals( 19 | listOf(OneObjectSealedInterfaceEnum.OneObjectSealedInterface_FirstObject), 20 | enumValues().toList() 21 | ) 22 | } 23 | 24 | @Test 25 | fun `one enum for sealed interface with mapping`() { 26 | assertEquals( 27 | OneObjectSealedInterface.values.map(OneObjectSealedInterface::enum), 28 | enumValues().toList() 29 | ) 30 | } 31 | 32 | @Test 33 | fun `correct enum class`() { 34 | assertEquals(OneObjectSealedInterfaceEnum::class, OneObjectSealedInterface.sealedEnum.enumClass) 35 | } 36 | 37 | @Test 38 | fun `compilation generates correct code`() { 39 | val result = compile(getCommonSourceFile("compilation", "basic", "OneObjectSealedInterface.kt")) 40 | 41 | assertCompiles(result) 42 | assertGeneratedFileMatches( 43 | "OneObjectSealedInterface_SealedEnum.kt", 44 | oneObjectSealedInterfaceGenerated, 45 | result 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/TwoObjectSealedClass.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed class TwoObjectSealedClass { 7 | object FirstObject : TwoObjectSealedClass() 8 | 9 | object SecondObject : TwoObjectSealedClass() 10 | 11 | @GenSealedEnum(generateEnum = true) 12 | companion object 13 | } 14 | 15 | @Language("kotlin") 16 | val twoObjectSealedClassGenerated = """ 17 | package com.livefront.sealedenum.compilation.basic 18 | 19 | import com.livefront.sealedenum.EnumForSealedEnumProvider 20 | import com.livefront.sealedenum.SealedEnum 21 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 22 | import kotlin.Int 23 | import kotlin.LazyThreadSafetyMode 24 | import kotlin.String 25 | import kotlin.collections.List 26 | import kotlin.reflect.KClass 27 | 28 | /** 29 | * An isomorphic enum for the sealed class [TwoObjectSealedClass] 30 | */ 31 | public enum class TwoObjectSealedClassEnum() { 32 | TwoObjectSealedClass_FirstObject, 33 | TwoObjectSealedClass_SecondObject, 34 | } 35 | 36 | /** 37 | * The isomorphic [TwoObjectSealedClassEnum] for [this]. 38 | */ 39 | public val TwoObjectSealedClass.`enum`: TwoObjectSealedClassEnum 40 | get() = TwoObjectSealedClassSealedEnum.sealedObjectToEnum(this) 41 | 42 | /** 43 | * The isomorphic [TwoObjectSealedClass] for [this]. 44 | */ 45 | public val TwoObjectSealedClassEnum.sealedObject: TwoObjectSealedClass 46 | get() = TwoObjectSealedClassSealedEnum.enumToSealedObject(this) 47 | 48 | /** 49 | * An implementation of [SealedEnum] for the sealed class [TwoObjectSealedClass] 50 | */ 51 | public object TwoObjectSealedClassSealedEnum : SealedEnum, 52 | SealedEnumWithEnumProvider, 53 | EnumForSealedEnumProvider { 54 | public override val values: List by lazy(mode = 55 | LazyThreadSafetyMode.PUBLICATION) { 56 | listOf( 57 | TwoObjectSealedClass.FirstObject, 58 | TwoObjectSealedClass.SecondObject 59 | ) 60 | } 61 | 62 | 63 | public override val enumClass: KClass 64 | get() = TwoObjectSealedClassEnum::class 65 | 66 | public override fun ordinalOf(obj: TwoObjectSealedClass): Int = when (obj) { 67 | is TwoObjectSealedClass.FirstObject -> 0 68 | is TwoObjectSealedClass.SecondObject -> 1 69 | } 70 | 71 | public override fun nameOf(obj: TwoObjectSealedClass): String = when (obj) { 72 | is TwoObjectSealedClass.FirstObject -> "TwoObjectSealedClass_FirstObject" 73 | is TwoObjectSealedClass.SecondObject -> "TwoObjectSealedClass_SecondObject" 74 | } 75 | 76 | public override fun valueOf(name: String): TwoObjectSealedClass = when (name) { 77 | "TwoObjectSealedClass_FirstObject" -> TwoObjectSealedClass.FirstObject 78 | "TwoObjectSealedClass_SecondObject" -> TwoObjectSealedClass.SecondObject 79 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 80 | } 81 | 82 | public override fun sealedObjectToEnum(obj: TwoObjectSealedClass): TwoObjectSealedClassEnum = 83 | when (obj) { 84 | is TwoObjectSealedClass.FirstObject -> 85 | TwoObjectSealedClassEnum.TwoObjectSealedClass_FirstObject 86 | is TwoObjectSealedClass.SecondObject -> 87 | TwoObjectSealedClassEnum.TwoObjectSealedClass_SecondObject 88 | } 89 | 90 | public override fun enumToSealedObject(`enum`: TwoObjectSealedClassEnum): TwoObjectSealedClass = 91 | when (enum) { 92 | TwoObjectSealedClassEnum.TwoObjectSealedClass_FirstObject -> 93 | TwoObjectSealedClass.FirstObject 94 | TwoObjectSealedClassEnum.TwoObjectSealedClass_SecondObject -> 95 | TwoObjectSealedClass.SecondObject 96 | } 97 | } 98 | 99 | /** 100 | * The index of [this] in the values list. 101 | */ 102 | public val TwoObjectSealedClass.ordinal: Int 103 | get() = TwoObjectSealedClassSealedEnum.ordinalOf(this) 104 | 105 | /** 106 | * The name of [this] for use with valueOf. 107 | */ 108 | public val TwoObjectSealedClass.name: String 109 | get() = TwoObjectSealedClassSealedEnum.nameOf(this) 110 | 111 | /** 112 | * A list of all [TwoObjectSealedClass] objects. 113 | */ 114 | public val TwoObjectSealedClass.Companion.values: List 115 | get() = TwoObjectSealedClassSealedEnum.values 116 | 117 | /** 118 | * Returns an implementation of [SealedEnum] for the sealed class [TwoObjectSealedClass] 119 | */ 120 | public val TwoObjectSealedClass.Companion.sealedEnum: TwoObjectSealedClassSealedEnum 121 | get() = TwoObjectSealedClassSealedEnum 122 | 123 | /** 124 | * Returns the [TwoObjectSealedClass] object for the given [name]. 125 | * 126 | * If the given name doesn't correspond to any [TwoObjectSealedClass], an [IllegalArgumentException] 127 | * will be thrown. 128 | */ 129 | public fun TwoObjectSealedClass.Companion.valueOf(name: String): TwoObjectSealedClass = 130 | TwoObjectSealedClassSealedEnum.valueOf(name) 131 | 132 | """.trimIndent() 133 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/TwoObjectSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class TwoObjectSealedClassTests { 11 | @Test 12 | fun `two objects sealed class`() { 13 | assertEquals( 14 | listOf(TwoObjectSealedClass.FirstObject, TwoObjectSealedClass.SecondObject), 15 | TwoObjectSealedClassSealedEnum.values 16 | ) 17 | } 18 | 19 | @Test 20 | fun `two enums for sealed class`() { 21 | assertEquals( 22 | listOf( 23 | TwoObjectSealedClassEnum.TwoObjectSealedClass_FirstObject, 24 | TwoObjectSealedClassEnum.TwoObjectSealedClass_SecondObject 25 | ), 26 | enumValues().toList() 27 | ) 28 | } 29 | 30 | @Test 31 | fun `two enums for sealed class with mapping`() { 32 | assertEquals( 33 | TwoObjectSealedClass.values.map(TwoObjectSealedClass::enum), 34 | enumValues().toList() 35 | ) 36 | } 37 | 38 | @Test 39 | fun `correct enum class`() { 40 | assertEquals(TwoObjectSealedClassEnum::class, TwoObjectSealedClassSealedEnum.enumClass) 41 | } 42 | 43 | @Test 44 | fun `compilation generates correct code`() { 45 | val result = compile(getCommonSourceFile("compilation", "basic", "TwoObjectSealedClass.kt")) 46 | 47 | assertCompiles(result) 48 | assertGeneratedFileMatches("TwoObjectSealedClass_SealedEnum.kt", twoObjectSealedClassGenerated, result) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/TwoObjectSealedInterface.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed interface TwoObjectSealedInterface { 7 | object FirstObject : TwoObjectSealedInterface 8 | 9 | object SecondObject : TwoObjectSealedInterface 10 | 11 | @GenSealedEnum(generateEnum = true) 12 | companion object 13 | } 14 | 15 | @Language("kotlin") 16 | val twoObjectSealedInterfaceGenerated = """ 17 | package com.livefront.sealedenum.compilation.basic 18 | 19 | import com.livefront.sealedenum.EnumForSealedEnumProvider 20 | import com.livefront.sealedenum.SealedEnum 21 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 22 | import kotlin.Int 23 | import kotlin.LazyThreadSafetyMode 24 | import kotlin.String 25 | import kotlin.collections.List 26 | import kotlin.reflect.KClass 27 | 28 | /** 29 | * An isomorphic enum for the sealed class [TwoObjectSealedInterface] 30 | */ 31 | public enum class TwoObjectSealedInterfaceEnum() { 32 | TwoObjectSealedInterface_FirstObject, 33 | TwoObjectSealedInterface_SecondObject, 34 | } 35 | 36 | /** 37 | * The isomorphic [TwoObjectSealedInterfaceEnum] for [this]. 38 | */ 39 | public val TwoObjectSealedInterface.`enum`: TwoObjectSealedInterfaceEnum 40 | get() = TwoObjectSealedInterfaceSealedEnum.sealedObjectToEnum(this) 41 | 42 | /** 43 | * The isomorphic [TwoObjectSealedInterface] for [this]. 44 | */ 45 | public val TwoObjectSealedInterfaceEnum.sealedObject: TwoObjectSealedInterface 46 | get() = TwoObjectSealedInterfaceSealedEnum.enumToSealedObject(this) 47 | 48 | /** 49 | * An implementation of [SealedEnum] for the sealed class [TwoObjectSealedInterface] 50 | */ 51 | public object TwoObjectSealedInterfaceSealedEnum : SealedEnum, 52 | SealedEnumWithEnumProvider, 53 | EnumForSealedEnumProvider { 54 | public override val values: List by lazy(mode = 55 | LazyThreadSafetyMode.PUBLICATION) { 56 | listOf( 57 | TwoObjectSealedInterface.FirstObject, 58 | TwoObjectSealedInterface.SecondObject 59 | ) 60 | } 61 | 62 | 63 | public override val enumClass: KClass 64 | get() = TwoObjectSealedInterfaceEnum::class 65 | 66 | public override fun ordinalOf(obj: TwoObjectSealedInterface): Int = when (obj) { 67 | is TwoObjectSealedInterface.FirstObject -> 0 68 | is TwoObjectSealedInterface.SecondObject -> 1 69 | } 70 | 71 | public override fun nameOf(obj: TwoObjectSealedInterface): String = when (obj) { 72 | is TwoObjectSealedInterface.FirstObject -> "TwoObjectSealedInterface_FirstObject" 73 | is TwoObjectSealedInterface.SecondObject -> "TwoObjectSealedInterface_SecondObject" 74 | } 75 | 76 | public override fun valueOf(name: String): TwoObjectSealedInterface = when (name) { 77 | "TwoObjectSealedInterface_FirstObject" -> TwoObjectSealedInterface.FirstObject 78 | "TwoObjectSealedInterface_SecondObject" -> TwoObjectSealedInterface.SecondObject 79 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 80 | } 81 | 82 | public override fun sealedObjectToEnum(obj: TwoObjectSealedInterface): 83 | TwoObjectSealedInterfaceEnum = when (obj) { 84 | is TwoObjectSealedInterface.FirstObject -> 85 | TwoObjectSealedInterfaceEnum.TwoObjectSealedInterface_FirstObject 86 | is TwoObjectSealedInterface.SecondObject -> 87 | TwoObjectSealedInterfaceEnum.TwoObjectSealedInterface_SecondObject 88 | } 89 | 90 | public override fun enumToSealedObject(`enum`: TwoObjectSealedInterfaceEnum): 91 | TwoObjectSealedInterface = when (enum) { 92 | TwoObjectSealedInterfaceEnum.TwoObjectSealedInterface_FirstObject -> 93 | TwoObjectSealedInterface.FirstObject 94 | TwoObjectSealedInterfaceEnum.TwoObjectSealedInterface_SecondObject -> 95 | TwoObjectSealedInterface.SecondObject 96 | } 97 | } 98 | 99 | /** 100 | * The index of [this] in the values list. 101 | */ 102 | public val TwoObjectSealedInterface.ordinal: Int 103 | get() = TwoObjectSealedInterfaceSealedEnum.ordinalOf(this) 104 | 105 | /** 106 | * The name of [this] for use with valueOf. 107 | */ 108 | public val TwoObjectSealedInterface.name: String 109 | get() = TwoObjectSealedInterfaceSealedEnum.nameOf(this) 110 | 111 | /** 112 | * A list of all [TwoObjectSealedInterface] objects. 113 | */ 114 | public val TwoObjectSealedInterface.Companion.values: List 115 | get() = TwoObjectSealedInterfaceSealedEnum.values 116 | 117 | /** 118 | * Returns an implementation of [SealedEnum] for the sealed class [TwoObjectSealedInterface] 119 | */ 120 | public val TwoObjectSealedInterface.Companion.sealedEnum: TwoObjectSealedInterfaceSealedEnum 121 | get() = TwoObjectSealedInterfaceSealedEnum 122 | 123 | /** 124 | * Returns the [TwoObjectSealedInterface] object for the given [name]. 125 | * 126 | * If the given name doesn't correspond to any [TwoObjectSealedInterface], an 127 | * [IllegalArgumentException] will be thrown. 128 | */ 129 | public fun TwoObjectSealedInterface.Companion.valueOf(name: String): TwoObjectSealedInterface = 130 | TwoObjectSealedInterfaceSealedEnum.valueOf(name) 131 | 132 | """.trimIndent() 133 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/basic/TwoObjectSealedInterfaceTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.basic 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class TwoObjectSealedInterfaceTests { 11 | @Test 12 | fun `two objects sealed interface`() { 13 | assertEquals( 14 | listOf(TwoObjectSealedInterface.FirstObject, TwoObjectSealedInterface.SecondObject), 15 | TwoObjectSealedInterface.values 16 | ) 17 | } 18 | 19 | @Test 20 | fun `two enums for sealed interface`() { 21 | assertEquals( 22 | listOf( 23 | TwoObjectSealedInterfaceEnum.TwoObjectSealedInterface_FirstObject, 24 | TwoObjectSealedInterfaceEnum.TwoObjectSealedInterface_SecondObject 25 | ), 26 | enumValues().toList() 27 | ) 28 | } 29 | 30 | @Test 31 | fun `two enums for sealed interface with mapping`() { 32 | assertEquals( 33 | TwoObjectSealedInterface.values.map(TwoObjectSealedInterface::enum), 34 | enumValues().toList() 35 | ) 36 | } 37 | 38 | @Test 39 | fun `correct enum class`() { 40 | assertEquals(TwoObjectSealedInterfaceEnum::class, TwoObjectSealedInterface.sealedEnum.enumClass) 41 | } 42 | 43 | @Test 44 | fun `compilation generates correct code`() { 45 | val result = compile(getCommonSourceFile("compilation", "basic", "TwoObjectSealedInterface.kt")) 46 | 47 | assertCompiles(result) 48 | assertGeneratedFileMatches( 49 | "TwoObjectSealedInterface_SealedEnum.kt", 50 | twoObjectSealedInterfaceGenerated, 51 | result 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/generics/GenericSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.generics 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class GenericSealedClassTests { 11 | 12 | @Test 13 | fun `one type parameter sealed class`() { 14 | assertEquals( 15 | listOf( 16 | OneTypeParameterSealedClass.FirstObject, 17 | OneTypeParameterSealedClass.SecondObject, 18 | OneTypeParameterSealedClass.ThirdObject 19 | ), 20 | OneTypeParameterSealedClass.values 21 | ) 22 | } 23 | 24 | @Test 25 | fun `compilation for one type parameter generates correct code`() { 26 | val result = compile(getCommonSourceFile("compilation", "generics", "GenericSealedClass.kt")) 27 | 28 | assertCompiles(result) 29 | assertGeneratedFileMatches( 30 | "OneTypeParameterSealedClass_SealedEnum.kt", 31 | oneTypeParameterSealedClassGenerated, 32 | result 33 | ) 34 | } 35 | 36 | @Test 37 | fun `two type parameter sealed class`() { 38 | assertEquals( 39 | listOf( 40 | TwoTypeParameterSealedClass.FirstObject, 41 | TwoTypeParameterSealedClass.SecondObject 42 | ), 43 | TwoTypeParameterSealedClass.values 44 | ) 45 | } 46 | 47 | @Test 48 | fun `compilation for two type parameter generates correct code`() { 49 | val result = compile(getCommonSourceFile("compilation", "generics", "GenericSealedClass.kt")) 50 | 51 | assertCompiles(result) 52 | assertGeneratedFileMatches( 53 | "TwoTypeParameterSealedClass_SealedEnum.kt", 54 | twoTypeParameterSealedClassGenerated, 55 | result 56 | ) 57 | } 58 | 59 | @Test 60 | fun `limited type parameter sealed class`() { 61 | assertEquals( 62 | listOf( 63 | LimitedTypeParameterSealedClass.FirstObject, 64 | LimitedTypeParameterSealedClass.SecondObject 65 | ), 66 | LimitedTypeParameterSealedClass.values 67 | ) 68 | } 69 | 70 | @Test 71 | fun `compilation for limited type parameter generates correct code`() { 72 | val result = compile(getCommonSourceFile("compilation", "generics", "GenericSealedClass.kt")) 73 | 74 | assertCompiles(result) 75 | assertGeneratedFileMatches( 76 | "LimitedTypeParameterSealedClass_SealedEnum.kt", 77 | limitedTypeParameterSealedClassGenerated, 78 | result 79 | ) 80 | } 81 | 82 | @Test 83 | fun `multiple bounds sealed class`() { 84 | assertEquals( 85 | listOf( 86 | MultipleBoundsSealedClass.FirstObject 87 | ), 88 | MultipleBoundsSealedClass.values 89 | ) 90 | } 91 | 92 | @Test 93 | fun `compilation for multiple bounds sealed class generates correct code`() { 94 | val result = compile(getCommonSourceFile("compilation", "generics", "GenericSealedClass.kt")) 95 | 96 | assertCompiles(result) 97 | assertGeneratedFileMatches( 98 | "MultipleBoundsSealedClass_SealedEnum.kt", 99 | multipleBoundsSealedClassGenerated, 100 | result 101 | ) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/generics/SealedEnumWithAbstractBaseClassesTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.generics 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Assertions.assertTrue 9 | import org.junit.jupiter.api.Test 10 | import kotlin.reflect.KTypeProjection 11 | import kotlin.reflect.full.createType 12 | import kotlin.reflect.full.isSubtypeOf 13 | 14 | class SealedEnumWithAbstractBaseClassesTests { 15 | @Test 16 | fun `enum implements correct interfaces with type arguments`() { 17 | assertTrue( 18 | SealedEnumWithAbstractBaseClassesEnum::class.createType().isSubtypeOf( 19 | BaseClassInterface1::class.createType( 20 | arguments = listOf( 21 | KTypeProjection.invariant( 22 | BaseClassInterface1::class.createType( 23 | arguments = listOf( 24 | KTypeProjection.invariant(Any::class.createType(nullable = true)) 25 | ) 26 | ) 27 | ) 28 | ) 29 | ) 30 | ) 31 | ) 32 | 33 | assertTrue( 34 | SealedEnumWithAbstractBaseClassesEnum::class.createType().isSubtypeOf( 35 | BaseClassInterface2::class.createType( 36 | arguments = listOf( 37 | KTypeProjection.invariant(String::class.createType()) 38 | ) 39 | ) 40 | ) 41 | ) 42 | 43 | // Check for compilation 44 | val emptyValues1: Array>> = SealedEnumWithAbstractBaseClassesEnum.values() 45 | val emptyValues2: Array> = SealedEnumWithAbstractBaseClassesEnum.values() 46 | 47 | assertEquals( 48 | emptyList>(), 49 | emptyValues1.toList() 50 | ) 51 | 52 | assertEquals( 53 | emptyList>(), 54 | emptyValues2.toList() 55 | ) 56 | } 57 | 58 | @Test 59 | fun `compilation for invariant type generates correct code`() { 60 | val result = compile(getCommonSourceFile("compilation", "generics", "SealedEnumWithAbstractBaseClasses.kt")) 61 | 62 | assertCompiles(result) 63 | assertGeneratedFileMatches( 64 | "SealedEnumWithAbstractBaseClasses_SealedEnum.kt", 65 | sealedEnumWithAbstractBaseClassesGenerated, 66 | result 67 | ) 68 | } 69 | 70 | @Test 71 | fun `covariant type enum implements correct interfaces with type arguments`() { 72 | assertTrue( 73 | SealedEnumWithAbstractBaseClassesCovariantTypeEnum::class.createType().isSubtypeOf( 74 | BaseClassInterface3::class.createType( 75 | arguments = listOf( 76 | KTypeProjection.covariant( 77 | BaseClassInterface3::class.createType( 78 | arguments = listOf( 79 | KTypeProjection.covariant(Any::class.createType(nullable = true)) 80 | ) 81 | ) 82 | ) 83 | ) 84 | ) 85 | ) 86 | ) 87 | 88 | val emptyValues: Array>> = 89 | SealedEnumWithAbstractBaseClassesCovariantTypeEnum.values() 90 | 91 | assertEquals( 92 | emptyList>>(), 93 | emptyValues.toList() 94 | ) 95 | } 96 | 97 | @Test 98 | fun `compilation for covariant type generates correct code`() { 99 | val result = compile(getCommonSourceFile("compilation", "generics", "SealedEnumWithAbstractBaseClasses.kt")) 100 | 101 | assertCompiles(result) 102 | assertGeneratedFileMatches( 103 | "SealedEnumWithAbstractBaseClassesCovariantType_SealedEnum.kt", 104 | sealedEnumWithAbstractBaseClassesCovariantTypeGenerated, 105 | result 106 | ) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/NestedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class NestedClassTests { 11 | 12 | @Test 13 | fun `inside one class`() { 14 | assertEquals( 15 | listOf( 16 | OuterClass.InsideOneClassSealedClass.FirstObject, 17 | OuterClass.InsideOneClassSealedClass.SecondObject 18 | ), 19 | OuterClass.InsideOneClassSealedClass.values 20 | ) 21 | } 22 | 23 | @Test 24 | fun `compilation for inside one class generates correct code`() { 25 | val result = compile(getCommonSourceFile("compilation", "location", "NestedClass.kt")) 26 | 27 | assertCompiles(result) 28 | assertGeneratedFileMatches( 29 | "OuterClass.InsideOneClassSealedClass_SealedEnum.kt", 30 | insideOneClassSealedClassGenerated, 31 | result 32 | ) 33 | } 34 | 35 | @Test 36 | fun `inside two classes`() { 37 | assertEquals( 38 | listOf( 39 | FirstOuterClass.SecondOuterClass.InsideTwoClassesSealedClass.FirstObject, 40 | FirstOuterClass.SecondOuterClass.InsideTwoClassesSealedClass.SecondObject 41 | ), 42 | FirstOuterClass.SecondOuterClass.InsideTwoClassesSealedClass.values 43 | ) 44 | } 45 | 46 | @Test 47 | fun `compilation for inside two classes generates correct code`() { 48 | val result = compile(getCommonSourceFile("compilation", "location", "NestedClass.kt")) 49 | 50 | assertCompiles(result) 51 | assertGeneratedFileMatches( 52 | "FirstOuterClass.SecondOuterClass.InsideTwoClassesSealedClass_SealedEnum.kt", 53 | insideTwoClassesSealedClassGenerated, 54 | result 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/OutsideSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class OutsideSealedClassTests { 11 | 12 | @Test 13 | fun `one object outside sealed class`() { 14 | assertEquals(listOf(AlphaFirstObject), AlphaOutsideSealedClass.values) 15 | } 16 | 17 | @Test 18 | fun `one enum outside sealed class`() { 19 | assertEquals( 20 | listOf(AlphaOutsideSealedClassEnum.AlphaFirstObject), 21 | enumValues().toList() 22 | ) 23 | } 24 | 25 | @Test 26 | fun `one enum outside sealed class with mapping`() { 27 | assertEquals( 28 | AlphaOutsideSealedClass.values.map(AlphaOutsideSealedClass::enum), 29 | enumValues().toList() 30 | ) 31 | } 32 | 33 | @Test 34 | fun `compilation for alpha outside sealed class generates correct code`() { 35 | val result = compile(getCommonSourceFile("compilation", "location", "OutsideSealedClass.kt")) 36 | 37 | assertCompiles(result) 38 | assertGeneratedFileMatches("AlphaOutsideSealedClass_SealedEnum.kt", alphaOutsideSealedClassGenerated, result) 39 | } 40 | 41 | @Test 42 | fun `two objects outside sealed class`() { 43 | assertEquals( 44 | listOf( 45 | BetaFirstObject, 46 | BetaFourthObject, 47 | BetaSecondObject, 48 | BetaThirdObject, 49 | ), 50 | BetaOutsideSealedClass.values 51 | ) 52 | } 53 | 54 | @Test 55 | fun `two enums outside sealed class`() { 56 | assertEquals( 57 | listOf( 58 | BetaOutsideSealedClassEnum.BetaFirstObject, 59 | BetaOutsideSealedClassEnum.BetaFourthObject, 60 | BetaOutsideSealedClassEnum.BetaSecondObject, 61 | BetaOutsideSealedClassEnum.BetaThirdObject, 62 | ), 63 | enumValues().toList() 64 | ) 65 | } 66 | 67 | @Test 68 | fun `two enums outside sealed class with mapping`() { 69 | assertEquals( 70 | BetaOutsideSealedClass.values.map(BetaOutsideSealedClass::enum), 71 | enumValues().toList() 72 | ) 73 | } 74 | 75 | @Test 76 | fun `compilation for beta outside sealed class generates correct code`() { 77 | val result = compile(getCommonSourceFile("compilation", "location", "OutsideSealedClass.kt")) 78 | 79 | assertCompiles(result) 80 | assertGeneratedFileMatches("BetaOutsideSealedClass_SealedEnum.kt", betaOutsideSealedClassGenerated, result) 81 | } 82 | 83 | @Test 84 | fun `outside object ordering`() { 85 | assertEquals( 86 | listOf( 87 | GammaOutsideSealedClass.GammaSecondObject, 88 | GammaFirstObject, 89 | GammaFourthObject, 90 | GammaThirdObject, 91 | ), 92 | GammaOutsideSealedClass.values 93 | ) 94 | } 95 | 96 | @Test 97 | fun `outside enum ordering`() { 98 | assertEquals( 99 | listOf( 100 | GammaOutsideSealedClassEnum.GammaOutsideSealedClass_GammaSecondObject, 101 | GammaOutsideSealedClassEnum.GammaFirstObject, 102 | GammaOutsideSealedClassEnum.GammaFourthObject, 103 | GammaOutsideSealedClassEnum.GammaThirdObject, 104 | ), 105 | enumValues().toList() 106 | ) 107 | } 108 | 109 | @Test 110 | fun `outside enum ordering with mapping`() { 111 | assertEquals( 112 | GammaOutsideSealedClass.values.map(GammaOutsideSealedClass::enum), 113 | enumValues().toList() 114 | ) 115 | } 116 | 117 | @Test 118 | fun `compilation for gamma outside sealed class generates correct code`() { 119 | val result = compile(getCommonSourceFile("compilation", "location", "OutsideSealedClass.kt")) 120 | 121 | assertCompiles(result) 122 | assertGeneratedFileMatches("GammaOutsideSealedClass_SealedEnum.kt", gammaOutsideSealedClassGenerated, result) 123 | } 124 | 125 | @Test 126 | fun `duplicate name objects`() { 127 | assertEquals( 128 | listOf(DeltaOutsideSealedClass.DeltaObject, DeltaObject), 129 | DeltaOutsideSealedClass.values 130 | ) 131 | } 132 | 133 | @Test 134 | fun `duplicate name enums`() { 135 | assertEquals( 136 | listOf( 137 | DeltaOutsideSealedClassEnum.DeltaOutsideSealedClass_DeltaObject, 138 | DeltaOutsideSealedClassEnum.DeltaObject, 139 | ), 140 | enumValues().toList() 141 | ) 142 | } 143 | 144 | @Test 145 | fun `duplicate name enums with mapping`() { 146 | assertEquals( 147 | DeltaOutsideSealedClass.values.map(DeltaOutsideSealedClass::enum), 148 | enumValues().toList() 149 | ) 150 | } 151 | 152 | @Test 153 | fun `compilation for delta outside sealed class generates correct code`() { 154 | val result = compile(getCommonSourceFile("compilation", "location", "OutsideSealedClass.kt")) 155 | 156 | assertCompiles(result) 157 | assertGeneratedFileMatches("DeltaOutsideSealedClass_SealedEnum.kt", deltaOutsideSealedClassGenerated, result) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/SplitAcrossFilesSealedClass.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed class SplitAcrossFilesSealedClass { 7 | 8 | @GenSealedEnum(generateEnum = true) 9 | companion object 10 | } 11 | 12 | @Language("kotlin") 13 | val splitAcrossFilesSealedClassGenerated = """ 14 | package com.livefront.sealedenum.compilation.location 15 | 16 | import com.livefront.sealedenum.EnumForSealedEnumProvider 17 | import com.livefront.sealedenum.SealedEnum 18 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 19 | import kotlin.Int 20 | import kotlin.LazyThreadSafetyMode 21 | import kotlin.String 22 | import kotlin.collections.List 23 | import kotlin.reflect.KClass 24 | 25 | /** 26 | * An isomorphic enum for the sealed class [SplitAcrossFilesSealedClass] 27 | */ 28 | public enum class SplitAcrossFilesSealedClassEnum() { 29 | SplitAcrossFilesSubclassA, 30 | SplitAcrossFilesSubclassB, 31 | SplitAcrossFilesSubclassC, 32 | } 33 | 34 | /** 35 | * The isomorphic [SplitAcrossFilesSealedClassEnum] for [this]. 36 | */ 37 | public val SplitAcrossFilesSealedClass.`enum`: SplitAcrossFilesSealedClassEnum 38 | get() = SplitAcrossFilesSealedClassSealedEnum.sealedObjectToEnum(this) 39 | 40 | /** 41 | * The isomorphic [SplitAcrossFilesSealedClass] for [this]. 42 | */ 43 | public val SplitAcrossFilesSealedClassEnum.sealedObject: SplitAcrossFilesSealedClass 44 | get() = SplitAcrossFilesSealedClassSealedEnum.enumToSealedObject(this) 45 | 46 | /** 47 | * An implementation of [SealedEnum] for the sealed class [SplitAcrossFilesSealedClass] 48 | */ 49 | public object SplitAcrossFilesSealedClassSealedEnum : SealedEnum, 50 | SealedEnumWithEnumProvider, 51 | EnumForSealedEnumProvider { 52 | public override val values: List by lazy(mode = 53 | LazyThreadSafetyMode.PUBLICATION) { 54 | listOf( 55 | SplitAcrossFilesSubclassA, 56 | SplitAcrossFilesSubclassB, 57 | SplitAcrossFilesSubclassC 58 | ) 59 | } 60 | 61 | 62 | public override val enumClass: KClass 63 | get() = SplitAcrossFilesSealedClassEnum::class 64 | 65 | public override fun ordinalOf(obj: SplitAcrossFilesSealedClass): Int = when (obj) { 66 | is SplitAcrossFilesSubclassA -> 0 67 | is SplitAcrossFilesSubclassB -> 1 68 | is SplitAcrossFilesSubclassC -> 2 69 | } 70 | 71 | public override fun nameOf(obj: SplitAcrossFilesSealedClass): String = when (obj) { 72 | is SplitAcrossFilesSubclassA -> "SplitAcrossFilesSubclassA" 73 | is SplitAcrossFilesSubclassB -> "SplitAcrossFilesSubclassB" 74 | is SplitAcrossFilesSubclassC -> "SplitAcrossFilesSubclassC" 75 | } 76 | 77 | public override fun valueOf(name: String): SplitAcrossFilesSealedClass = when (name) { 78 | "SplitAcrossFilesSubclassA" -> SplitAcrossFilesSubclassA 79 | "SplitAcrossFilesSubclassB" -> SplitAcrossFilesSubclassB 80 | "SplitAcrossFilesSubclassC" -> SplitAcrossFilesSubclassC 81 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 82 | } 83 | 84 | public override fun sealedObjectToEnum(obj: SplitAcrossFilesSealedClass): 85 | SplitAcrossFilesSealedClassEnum = when (obj) { 86 | is SplitAcrossFilesSubclassA -> SplitAcrossFilesSealedClassEnum.SplitAcrossFilesSubclassA 87 | is SplitAcrossFilesSubclassB -> SplitAcrossFilesSealedClassEnum.SplitAcrossFilesSubclassB 88 | is SplitAcrossFilesSubclassC -> SplitAcrossFilesSealedClassEnum.SplitAcrossFilesSubclassC 89 | } 90 | 91 | public override fun enumToSealedObject(`enum`: SplitAcrossFilesSealedClassEnum): 92 | SplitAcrossFilesSealedClass = when (enum) { 93 | SplitAcrossFilesSealedClassEnum.SplitAcrossFilesSubclassA -> SplitAcrossFilesSubclassA 94 | SplitAcrossFilesSealedClassEnum.SplitAcrossFilesSubclassB -> SplitAcrossFilesSubclassB 95 | SplitAcrossFilesSealedClassEnum.SplitAcrossFilesSubclassC -> SplitAcrossFilesSubclassC 96 | } 97 | } 98 | 99 | /** 100 | * The index of [this] in the values list. 101 | */ 102 | public val SplitAcrossFilesSealedClass.ordinal: Int 103 | get() = SplitAcrossFilesSealedClassSealedEnum.ordinalOf(this) 104 | 105 | /** 106 | * The name of [this] for use with valueOf. 107 | */ 108 | public val SplitAcrossFilesSealedClass.name: String 109 | get() = SplitAcrossFilesSealedClassSealedEnum.nameOf(this) 110 | 111 | /** 112 | * A list of all [SplitAcrossFilesSealedClass] objects. 113 | */ 114 | public val SplitAcrossFilesSealedClass.Companion.values: List 115 | get() = SplitAcrossFilesSealedClassSealedEnum.values 116 | 117 | /** 118 | * Returns an implementation of [SealedEnum] for the sealed class [SplitAcrossFilesSealedClass] 119 | */ 120 | public val SplitAcrossFilesSealedClass.Companion.sealedEnum: SplitAcrossFilesSealedClassSealedEnum 121 | get() = SplitAcrossFilesSealedClassSealedEnum 122 | 123 | /** 124 | * Returns the [SplitAcrossFilesSealedClass] object for the given [name]. 125 | * 126 | * If the given name doesn't correspond to any [SplitAcrossFilesSealedClass], an 127 | * [IllegalArgumentException] will be thrown. 128 | */ 129 | public fun SplitAcrossFilesSealedClass.Companion.valueOf(name: String): SplitAcrossFilesSealedClass 130 | = SplitAcrossFilesSealedClassSealedEnum.valueOf(name) 131 | 132 | """.trimIndent() 133 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/SplitAcrossFilesSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class SplitAcrossFilesSealedClassTests { 11 | 12 | @Test 13 | fun `object split across files`() { 14 | assertEquals( 15 | listOf( 16 | SplitAcrossFilesSubclassA, 17 | SplitAcrossFilesSubclassB, 18 | SplitAcrossFilesSubclassC 19 | ), 20 | SplitAcrossFilesSealedClass.values 21 | ) 22 | } 23 | 24 | @Test 25 | fun `enum for objects split across files`() { 26 | assertEquals( 27 | SplitAcrossFilesSealedClass.values.map(SplitAcrossFilesSealedClass::enum), 28 | enumValues().toList() 29 | ) 30 | } 31 | 32 | @Test 33 | fun `compilation for objects split across files generates correct code`() { 34 | val result = compile( 35 | getCommonSourceFile("compilation", "location", "SplitAcrossFilesSealedClass.kt"), 36 | getCommonSourceFile("compilation", "location", "SplitAcrossFilesSubclassA.kt"), 37 | getCommonSourceFile("compilation", "location", "SplitAcrossFilesSubclassB.kt"), 38 | getCommonSourceFile("compilation", "location", "SplitAcrossFilesSubclassC.kt") 39 | ) 40 | 41 | assertCompiles(result) 42 | assertGeneratedFileMatches( 43 | "SplitAcrossFilesSealedClass_SealedEnum.kt", 44 | splitAcrossFilesSealedClassGenerated, 45 | result 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/SplitAcrossFilesSubclassA.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | object SplitAcrossFilesSubclassA : SplitAcrossFilesSealedClass() 4 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/SplitAcrossFilesSubclassB.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | object SplitAcrossFilesSubclassB : SplitAcrossFilesSealedClass() 4 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/location/SplitAcrossFilesSubclassC.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.location 2 | 3 | object SplitAcrossFilesSubclassC : SplitAcrossFilesSealedClass() 4 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/usecases/EnvironmentsSealedEnum.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.usecases 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | interface Uri { 7 | val scheme: String 8 | val authority: String 9 | val path: String 10 | } 11 | 12 | sealed class Environments( 13 | override val scheme: String, 14 | override val authority: String, 15 | override val path: String 16 | ) : Uri { 17 | 18 | sealed class Http( 19 | override val authority: String, 20 | override val path: String 21 | ) : Environments("http", authority, path) { 22 | object Livefront : Http("www.livefront.com", "/") 23 | object Google : Http("www.google.com", "/") 24 | } 25 | 26 | sealed class Https( 27 | override val authority: String, 28 | override val path: String 29 | ) : Environments("https", authority, path) { 30 | object Livefront : Https("www.livefront.com", "/") 31 | object Google : Https("www.google.com", "/") 32 | } 33 | 34 | @GenSealedEnum(generateEnum = true) 35 | companion object 36 | } 37 | 38 | @Language("kotlin") 39 | val environmentsGenerated = """ 40 | package com.livefront.sealedenum.compilation.usecases 41 | 42 | import com.livefront.sealedenum.EnumForSealedEnumProvider 43 | import com.livefront.sealedenum.SealedEnum 44 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 45 | import kotlin.Int 46 | import kotlin.LazyThreadSafetyMode 47 | import kotlin.String 48 | import kotlin.collections.List 49 | import kotlin.reflect.KClass 50 | 51 | /** 52 | * An isomorphic enum for the sealed class [Environments] 53 | */ 54 | public enum class EnvironmentsEnum( 55 | sealedObject: Environments, 56 | ) : Uri by sealedObject { 57 | Environments_Http_Livefront(com.livefront.sealedenum.compilation.usecases.Environments.Http.Livefront), 58 | Environments_Http_Google(com.livefront.sealedenum.compilation.usecases.Environments.Http.Google), 59 | Environments_Https_Livefront(com.livefront.sealedenum.compilation.usecases.Environments.Https.Livefront), 60 | Environments_Https_Google(com.livefront.sealedenum.compilation.usecases.Environments.Https.Google), 61 | } 62 | 63 | /** 64 | * The isomorphic [EnvironmentsEnum] for [this]. 65 | */ 66 | public val Environments.`enum`: EnvironmentsEnum 67 | get() = EnvironmentsSealedEnum.sealedObjectToEnum(this) 68 | 69 | /** 70 | * The isomorphic [Environments] for [this]. 71 | */ 72 | public val EnvironmentsEnum.sealedObject: Environments 73 | get() = EnvironmentsSealedEnum.enumToSealedObject(this) 74 | 75 | /** 76 | * An implementation of [SealedEnum] for the sealed class [Environments] 77 | */ 78 | public object EnvironmentsSealedEnum : SealedEnum, 79 | SealedEnumWithEnumProvider, 80 | EnumForSealedEnumProvider { 81 | public override val values: List by lazy(mode = 82 | LazyThreadSafetyMode.PUBLICATION) { 83 | listOf( 84 | Environments.Http.Livefront, 85 | Environments.Http.Google, 86 | Environments.Https.Livefront, 87 | Environments.Https.Google 88 | ) 89 | } 90 | 91 | 92 | public override val enumClass: KClass 93 | get() = EnvironmentsEnum::class 94 | 95 | public override fun ordinalOf(obj: Environments): Int = when (obj) { 96 | is Environments.Http.Livefront -> 0 97 | is Environments.Http.Google -> 1 98 | is Environments.Https.Livefront -> 2 99 | is Environments.Https.Google -> 3 100 | } 101 | 102 | public override fun nameOf(obj: Environments): String = when (obj) { 103 | is Environments.Http.Livefront -> "Environments_Http_Livefront" 104 | is Environments.Http.Google -> "Environments_Http_Google" 105 | is Environments.Https.Livefront -> "Environments_Https_Livefront" 106 | is Environments.Https.Google -> "Environments_Https_Google" 107 | } 108 | 109 | public override fun valueOf(name: String): Environments = when (name) { 110 | "Environments_Http_Livefront" -> Environments.Http.Livefront 111 | "Environments_Http_Google" -> Environments.Http.Google 112 | "Environments_Https_Livefront" -> Environments.Https.Livefront 113 | "Environments_Https_Google" -> Environments.Https.Google 114 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 115 | } 116 | 117 | public override fun sealedObjectToEnum(obj: Environments): EnvironmentsEnum = when (obj) { 118 | is Environments.Http.Livefront -> EnvironmentsEnum.Environments_Http_Livefront 119 | is Environments.Http.Google -> EnvironmentsEnum.Environments_Http_Google 120 | is Environments.Https.Livefront -> EnvironmentsEnum.Environments_Https_Livefront 121 | is Environments.Https.Google -> EnvironmentsEnum.Environments_Https_Google 122 | } 123 | 124 | public override fun enumToSealedObject(`enum`: EnvironmentsEnum): Environments = when (enum) { 125 | EnvironmentsEnum.Environments_Http_Livefront -> Environments.Http.Livefront 126 | EnvironmentsEnum.Environments_Http_Google -> Environments.Http.Google 127 | EnvironmentsEnum.Environments_Https_Livefront -> Environments.Https.Livefront 128 | EnvironmentsEnum.Environments_Https_Google -> Environments.Https.Google 129 | } 130 | } 131 | 132 | /** 133 | * The index of [this] in the values list. 134 | */ 135 | public val Environments.ordinal: Int 136 | get() = EnvironmentsSealedEnum.ordinalOf(this) 137 | 138 | /** 139 | * The name of [this] for use with valueOf. 140 | */ 141 | public val Environments.name: String 142 | get() = EnvironmentsSealedEnum.nameOf(this) 143 | 144 | /** 145 | * A list of all [Environments] objects. 146 | */ 147 | public val Environments.Companion.values: List 148 | get() = EnvironmentsSealedEnum.values 149 | 150 | /** 151 | * Returns an implementation of [SealedEnum] for the sealed class [Environments] 152 | */ 153 | public val Environments.Companion.sealedEnum: EnvironmentsSealedEnum 154 | get() = EnvironmentsSealedEnum 155 | 156 | /** 157 | * Returns the [Environments] object for the given [name]. 158 | * 159 | * If the given name doesn't correspond to any [Environments], an [IllegalArgumentException] will be 160 | * thrown. 161 | */ 162 | public fun Environments.Companion.valueOf(name: String): Environments = 163 | EnvironmentsSealedEnum.valueOf(name) 164 | 165 | """.trimIndent() 166 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/usecases/EnvironmentsSealedEnumTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.usecases 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class EnvironmentManager( 11 | enumClass: Class, 12 | defaultEnvironment: T 13 | ) where T : Enum, T : Uri { 14 | val environments = enumClass.enumConstants.map { 15 | "${it.scheme}://${it.authority}${it.path}" 16 | } 17 | 18 | var currentEnvironment = defaultEnvironment 19 | } 20 | 21 | class EnvironmentsSealedEnumTests { 22 | @Test 23 | fun `environment manager from direct enum`() { 24 | val environmentManager = EnvironmentManager( 25 | enumClass = EnvironmentsEnum::class.java, 26 | defaultEnvironment = EnvironmentsEnum.Environments_Https_Livefront 27 | ) 28 | 29 | assertEquals( 30 | listOf( 31 | "http://www.livefront.com/", 32 | "http://www.google.com/", 33 | "https://www.livefront.com/", 34 | "https://www.google.com/" 35 | ), 36 | environmentManager.environments 37 | ) 38 | 39 | assertEquals( 40 | EnvironmentsEnum.Environments_Https_Livefront, 41 | environmentManager.currentEnvironment 42 | ) 43 | } 44 | 45 | @Test 46 | fun `environment manager from sealed enum`() { 47 | val environmentManager = EnvironmentManager( 48 | enumClass = Environments.sealedEnum.enumClass.java, 49 | defaultEnvironment = Environments.Https.Livefront.enum 50 | ) 51 | 52 | assertEquals( 53 | listOf( 54 | "http://www.livefront.com/", 55 | "http://www.google.com/", 56 | "https://www.livefront.com/", 57 | "https://www.google.com/" 58 | ), 59 | environmentManager.environments 60 | ) 61 | 62 | assertEquals( 63 | Environments.Https.Livefront, 64 | environmentManager.currentEnvironment.sealedObject 65 | ) 66 | } 67 | 68 | @Test 69 | fun `compilation generates correct code`() { 70 | val result = compile(getCommonSourceFile("compilation", "usecases", "EnvironmentsSealedEnum.kt")) 71 | 72 | assertCompiles(result) 73 | assertGeneratedFileMatches("Environments_SealedEnum.kt", environmentsGenerated, result) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/usecases/Flag.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.usecases 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed class Flag { 7 | val i: Int = 1 shl ordinal 8 | object FirstFlag : Flag() 9 | 10 | object SecondFlag : Flag() 11 | 12 | @GenSealedEnum(generateEnum = true) 13 | companion object 14 | } 15 | 16 | @Language("kotlin") 17 | val flagGenerated = """ 18 | package com.livefront.sealedenum.compilation.usecases 19 | 20 | import com.livefront.sealedenum.EnumForSealedEnumProvider 21 | import com.livefront.sealedenum.SealedEnum 22 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 23 | import kotlin.Int 24 | import kotlin.LazyThreadSafetyMode 25 | import kotlin.String 26 | import kotlin.collections.List 27 | import kotlin.reflect.KClass 28 | 29 | /** 30 | * An isomorphic enum for the sealed class [Flag] 31 | */ 32 | public enum class FlagEnum() { 33 | Flag_FirstFlag, 34 | Flag_SecondFlag, 35 | } 36 | 37 | /** 38 | * The isomorphic [FlagEnum] for [this]. 39 | */ 40 | public val Flag.`enum`: FlagEnum 41 | get() = FlagSealedEnum.sealedObjectToEnum(this) 42 | 43 | /** 44 | * The isomorphic [Flag] for [this]. 45 | */ 46 | public val FlagEnum.sealedObject: Flag 47 | get() = FlagSealedEnum.enumToSealedObject(this) 48 | 49 | /** 50 | * An implementation of [SealedEnum] for the sealed class [Flag] 51 | */ 52 | public object FlagSealedEnum : SealedEnum, SealedEnumWithEnumProvider, 53 | EnumForSealedEnumProvider { 54 | public override val values: List by lazy(mode = LazyThreadSafetyMode.PUBLICATION) { 55 | listOf( 56 | Flag.FirstFlag, 57 | Flag.SecondFlag 58 | ) 59 | } 60 | 61 | 62 | public override val enumClass: KClass 63 | get() = FlagEnum::class 64 | 65 | public override fun ordinalOf(obj: Flag): Int = when (obj) { 66 | is Flag.FirstFlag -> 0 67 | is Flag.SecondFlag -> 1 68 | } 69 | 70 | public override fun nameOf(obj: Flag): String = when (obj) { 71 | is Flag.FirstFlag -> "Flag_FirstFlag" 72 | is Flag.SecondFlag -> "Flag_SecondFlag" 73 | } 74 | 75 | public override fun valueOf(name: String): Flag = when (name) { 76 | "Flag_FirstFlag" -> Flag.FirstFlag 77 | "Flag_SecondFlag" -> Flag.SecondFlag 78 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 79 | } 80 | 81 | public override fun sealedObjectToEnum(obj: Flag): FlagEnum = when (obj) { 82 | is Flag.FirstFlag -> FlagEnum.Flag_FirstFlag 83 | is Flag.SecondFlag -> FlagEnum.Flag_SecondFlag 84 | } 85 | 86 | public override fun enumToSealedObject(`enum`: FlagEnum): Flag = when (enum) { 87 | FlagEnum.Flag_FirstFlag -> Flag.FirstFlag 88 | FlagEnum.Flag_SecondFlag -> Flag.SecondFlag 89 | } 90 | } 91 | 92 | /** 93 | * The index of [this] in the values list. 94 | */ 95 | public val Flag.ordinal: Int 96 | get() = FlagSealedEnum.ordinalOf(this) 97 | 98 | /** 99 | * The name of [this] for use with valueOf. 100 | */ 101 | public val Flag.name: String 102 | get() = FlagSealedEnum.nameOf(this) 103 | 104 | /** 105 | * A list of all [Flag] objects. 106 | */ 107 | public val Flag.Companion.values: List 108 | get() = FlagSealedEnum.values 109 | 110 | /** 111 | * Returns an implementation of [SealedEnum] for the sealed class [Flag] 112 | */ 113 | public val Flag.Companion.sealedEnum: FlagSealedEnum 114 | get() = FlagSealedEnum 115 | 116 | /** 117 | * Returns the [Flag] object for the given [name]. 118 | * 119 | * If the given name doesn't correspond to any [Flag], an [IllegalArgumentException] will be thrown. 120 | */ 121 | public fun Flag.Companion.valueOf(name: String): Flag = FlagSealedEnum.valueOf(name) 122 | 123 | """.trimIndent() 124 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/usecases/FlagTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.usecases 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class FlagTests { 11 | @Test 12 | fun `two objects sealed class`() { 13 | assertEquals( 14 | listOf(Flag.FirstFlag, Flag.SecondFlag), 15 | FlagSealedEnum.values 16 | ) 17 | } 18 | 19 | @Test 20 | fun `two enums for sealed class`() { 21 | assertEquals( 22 | listOf( 23 | FlagEnum.Flag_FirstFlag, 24 | FlagEnum.Flag_SecondFlag 25 | ), 26 | enumValues().toList() 27 | ) 28 | } 29 | 30 | @Test 31 | fun `two enums for sealed class with mapping`() { 32 | assertEquals( 33 | Flag.values.map(Flag::enum), 34 | enumValues().toList() 35 | ) 36 | } 37 | 38 | @Test 39 | fun `correct enum class`() { 40 | assertEquals(FlagEnum::class, FlagSealedEnum.enumClass) 41 | } 42 | 43 | @Test 44 | fun `compilation generates correct code`() { 45 | val result = compile(getCommonSourceFile("compilation", "usecases", "Flag.kt")) 46 | 47 | assertCompiles(result) 48 | assertGeneratedFileMatches("Flag_SealedEnum.kt", flagGenerated, result) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/usecases/MultiInterfaceFlag.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.usecases 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed interface MultiInterfaceFlag { 7 | sealed interface FirstInterface : MultiInterfaceFlag 8 | sealed interface SecondInterface : MultiInterfaceFlag 9 | object FirstFlag : FirstInterface 10 | object SecondFlag : SecondInterface 11 | object BothFlags : FirstInterface, SecondInterface 12 | 13 | @GenSealedEnum(generateEnum = true) 14 | companion object 15 | } 16 | 17 | @Language("kotlin") 18 | val multiInterfaceFlagGenerated = """ 19 | package com.livefront.sealedenum.compilation.usecases 20 | 21 | import com.livefront.sealedenum.EnumForSealedEnumProvider 22 | import com.livefront.sealedenum.SealedEnum 23 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 24 | import kotlin.Int 25 | import kotlin.LazyThreadSafetyMode 26 | import kotlin.String 27 | import kotlin.collections.List 28 | import kotlin.reflect.KClass 29 | 30 | /** 31 | * An isomorphic enum for the sealed class [MultiInterfaceFlag] 32 | */ 33 | public enum class MultiInterfaceFlagEnum() { 34 | MultiInterfaceFlag_BothFlags, 35 | MultiInterfaceFlag_FirstFlag, 36 | MultiInterfaceFlag_SecondFlag, 37 | } 38 | 39 | /** 40 | * The isomorphic [MultiInterfaceFlagEnum] for [this]. 41 | */ 42 | public val MultiInterfaceFlag.`enum`: MultiInterfaceFlagEnum 43 | get() = MultiInterfaceFlagSealedEnum.sealedObjectToEnum(this) 44 | 45 | /** 46 | * The isomorphic [MultiInterfaceFlag] for [this]. 47 | */ 48 | public val MultiInterfaceFlagEnum.sealedObject: MultiInterfaceFlag 49 | get() = MultiInterfaceFlagSealedEnum.enumToSealedObject(this) 50 | 51 | /** 52 | * An implementation of [SealedEnum] for the sealed class [MultiInterfaceFlag] 53 | */ 54 | public object MultiInterfaceFlagSealedEnum : SealedEnum, 55 | SealedEnumWithEnumProvider, 56 | EnumForSealedEnumProvider { 57 | public override val values: List by lazy(mode = 58 | LazyThreadSafetyMode.PUBLICATION) { 59 | listOf( 60 | MultiInterfaceFlag.BothFlags, 61 | MultiInterfaceFlag.FirstFlag, 62 | MultiInterfaceFlag.SecondFlag 63 | ) 64 | } 65 | 66 | 67 | public override val enumClass: KClass 68 | get() = MultiInterfaceFlagEnum::class 69 | 70 | public override fun ordinalOf(obj: MultiInterfaceFlag): Int = when (obj) { 71 | is MultiInterfaceFlag.BothFlags -> 0 72 | is MultiInterfaceFlag.FirstFlag -> 1 73 | is MultiInterfaceFlag.SecondFlag -> 2 74 | } 75 | 76 | public override fun nameOf(obj: MultiInterfaceFlag): String = when (obj) { 77 | is MultiInterfaceFlag.BothFlags -> "MultiInterfaceFlag_BothFlags" 78 | is MultiInterfaceFlag.FirstFlag -> "MultiInterfaceFlag_FirstFlag" 79 | is MultiInterfaceFlag.SecondFlag -> "MultiInterfaceFlag_SecondFlag" 80 | } 81 | 82 | public override fun valueOf(name: String): MultiInterfaceFlag = when (name) { 83 | "MultiInterfaceFlag_BothFlags" -> MultiInterfaceFlag.BothFlags 84 | "MultiInterfaceFlag_FirstFlag" -> MultiInterfaceFlag.FirstFlag 85 | "MultiInterfaceFlag_SecondFlag" -> MultiInterfaceFlag.SecondFlag 86 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 87 | } 88 | 89 | public override fun sealedObjectToEnum(obj: MultiInterfaceFlag): MultiInterfaceFlagEnum = when 90 | (obj) { 91 | is MultiInterfaceFlag.BothFlags -> MultiInterfaceFlagEnum.MultiInterfaceFlag_BothFlags 92 | is MultiInterfaceFlag.FirstFlag -> MultiInterfaceFlagEnum.MultiInterfaceFlag_FirstFlag 93 | is MultiInterfaceFlag.SecondFlag -> MultiInterfaceFlagEnum.MultiInterfaceFlag_SecondFlag 94 | } 95 | 96 | public override fun enumToSealedObject(`enum`: MultiInterfaceFlagEnum): MultiInterfaceFlag = 97 | when (enum) { 98 | MultiInterfaceFlagEnum.MultiInterfaceFlag_BothFlags -> MultiInterfaceFlag.BothFlags 99 | MultiInterfaceFlagEnum.MultiInterfaceFlag_FirstFlag -> MultiInterfaceFlag.FirstFlag 100 | MultiInterfaceFlagEnum.MultiInterfaceFlag_SecondFlag -> MultiInterfaceFlag.SecondFlag 101 | } 102 | } 103 | 104 | /** 105 | * The index of [this] in the values list. 106 | */ 107 | public val MultiInterfaceFlag.ordinal: Int 108 | get() = MultiInterfaceFlagSealedEnum.ordinalOf(this) 109 | 110 | /** 111 | * The name of [this] for use with valueOf. 112 | */ 113 | public val MultiInterfaceFlag.name: String 114 | get() = MultiInterfaceFlagSealedEnum.nameOf(this) 115 | 116 | /** 117 | * A list of all [MultiInterfaceFlag] objects. 118 | */ 119 | public val MultiInterfaceFlag.Companion.values: List 120 | get() = MultiInterfaceFlagSealedEnum.values 121 | 122 | /** 123 | * Returns an implementation of [SealedEnum] for the sealed class [MultiInterfaceFlag] 124 | */ 125 | public val MultiInterfaceFlag.Companion.sealedEnum: MultiInterfaceFlagSealedEnum 126 | get() = MultiInterfaceFlagSealedEnum 127 | 128 | /** 129 | * Returns the [MultiInterfaceFlag] object for the given [name]. 130 | * 131 | * If the given name doesn't correspond to any [MultiInterfaceFlag], an [IllegalArgumentException] 132 | * will be thrown. 133 | */ 134 | public fun MultiInterfaceFlag.Companion.valueOf(name: String): MultiInterfaceFlag = 135 | MultiInterfaceFlagSealedEnum.valueOf(name) 136 | 137 | """.trimIndent() 138 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/usecases/MultiInterfaceFlagTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.usecases 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class MultiInterfaceFlagTests { 11 | @Test 12 | fun `three objects sealed interface`() { 13 | assertEquals( 14 | listOf(MultiInterfaceFlag.BothFlags, MultiInterfaceFlag.FirstFlag, MultiInterfaceFlag.SecondFlag), 15 | MultiInterfaceFlag.values 16 | ) 17 | } 18 | 19 | @Test 20 | fun `three enums for sealed class`() { 21 | assertEquals( 22 | listOf( 23 | MultiInterfaceFlagEnum.MultiInterfaceFlag_BothFlags, 24 | MultiInterfaceFlagEnum.MultiInterfaceFlag_FirstFlag, 25 | MultiInterfaceFlagEnum.MultiInterfaceFlag_SecondFlag 26 | ), 27 | enumValues().toList() 28 | ) 29 | } 30 | 31 | @Test 32 | fun `three enums for sealed interface with mapping`() { 33 | assertEquals( 34 | MultiInterfaceFlag.values.map(MultiInterfaceFlag::enum), 35 | enumValues().toList() 36 | ) 37 | } 38 | 39 | @Test 40 | fun `correct enum class`() { 41 | assertEquals(MultiInterfaceFlagEnum::class, MultiInterfaceFlagSealedEnum.enumClass) 42 | } 43 | 44 | @Test 45 | fun `compilation generates correct code`() { 46 | val result = compile(getCommonSourceFile("compilation", "usecases", "MultiInterfaceFlag.kt")) 47 | 48 | assertCompiles(result) 49 | assertGeneratedFileMatches("MultiInterfaceFlag_SealedEnum.kt", multiInterfaceFlagGenerated, result) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/visibility/PrivateInterfaceSealedClass.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | sealed class PrivateInterfaceSealedClass : 7 | JavaPrivateInterfaceSubclass(JavaPrivateInterfaceOuterClass()), PrivateInterface { 8 | 9 | object FirstObject : PrivateInterfaceSealedClass() 10 | 11 | object SecondObject : PrivateInterfaceSealedClass() 12 | 13 | @GenSealedEnum(generateEnum = true) 14 | companion object 15 | } 16 | 17 | private interface PrivateInterface 18 | 19 | @Language("kotlin") 20 | val privateInterfaceSealedClassGenerated = """ 21 | package com.livefront.sealedenum.compilation.visibility 22 | 23 | import com.livefront.sealedenum.EnumForSealedEnumProvider 24 | import com.livefront.sealedenum.SealedEnum 25 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 26 | import kotlin.Int 27 | import kotlin.LazyThreadSafetyMode 28 | import kotlin.String 29 | import kotlin.collections.List 30 | import kotlin.reflect.KClass 31 | 32 | /** 33 | * An isomorphic enum for the sealed class [PrivateInterfaceSealedClass] 34 | */ 35 | public enum class PrivateInterfaceSealedClassEnum() { 36 | PrivateInterfaceSealedClass_FirstObject, 37 | PrivateInterfaceSealedClass_SecondObject, 38 | } 39 | 40 | /** 41 | * The isomorphic [PrivateInterfaceSealedClassEnum] for [this]. 42 | */ 43 | public val PrivateInterfaceSealedClass.`enum`: PrivateInterfaceSealedClassEnum 44 | get() = PrivateInterfaceSealedClassSealedEnum.sealedObjectToEnum(this) 45 | 46 | /** 47 | * The isomorphic [PrivateInterfaceSealedClass] for [this]. 48 | */ 49 | public val PrivateInterfaceSealedClassEnum.sealedObject: PrivateInterfaceSealedClass 50 | get() = PrivateInterfaceSealedClassSealedEnum.enumToSealedObject(this) 51 | 52 | /** 53 | * An implementation of [SealedEnum] for the sealed class [PrivateInterfaceSealedClass] 54 | */ 55 | public object PrivateInterfaceSealedClassSealedEnum : SealedEnum, 56 | SealedEnumWithEnumProvider, 57 | EnumForSealedEnumProvider { 58 | public override val values: List by lazy(mode = 59 | LazyThreadSafetyMode.PUBLICATION) { 60 | listOf( 61 | PrivateInterfaceSealedClass.FirstObject, 62 | PrivateInterfaceSealedClass.SecondObject 63 | ) 64 | } 65 | 66 | 67 | public override val enumClass: KClass 68 | get() = PrivateInterfaceSealedClassEnum::class 69 | 70 | public override fun ordinalOf(obj: PrivateInterfaceSealedClass): Int = when (obj) { 71 | is PrivateInterfaceSealedClass.FirstObject -> 0 72 | is PrivateInterfaceSealedClass.SecondObject -> 1 73 | } 74 | 75 | public override fun nameOf(obj: PrivateInterfaceSealedClass): String = when (obj) { 76 | is PrivateInterfaceSealedClass.FirstObject -> "PrivateInterfaceSealedClass_FirstObject" 77 | is PrivateInterfaceSealedClass.SecondObject -> "PrivateInterfaceSealedClass_SecondObject" 78 | } 79 | 80 | public override fun valueOf(name: String): PrivateInterfaceSealedClass = when (name) { 81 | "PrivateInterfaceSealedClass_FirstObject" -> PrivateInterfaceSealedClass.FirstObject 82 | "PrivateInterfaceSealedClass_SecondObject" -> PrivateInterfaceSealedClass.SecondObject 83 | else -> throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 84 | } 85 | 86 | public override fun sealedObjectToEnum(obj: PrivateInterfaceSealedClass): 87 | PrivateInterfaceSealedClassEnum = when (obj) { 88 | is PrivateInterfaceSealedClass.FirstObject -> 89 | PrivateInterfaceSealedClassEnum.PrivateInterfaceSealedClass_FirstObject 90 | is PrivateInterfaceSealedClass.SecondObject -> 91 | PrivateInterfaceSealedClassEnum.PrivateInterfaceSealedClass_SecondObject 92 | } 93 | 94 | public override fun enumToSealedObject(`enum`: PrivateInterfaceSealedClassEnum): 95 | PrivateInterfaceSealedClass = when (enum) { 96 | PrivateInterfaceSealedClassEnum.PrivateInterfaceSealedClass_FirstObject -> 97 | PrivateInterfaceSealedClass.FirstObject 98 | PrivateInterfaceSealedClassEnum.PrivateInterfaceSealedClass_SecondObject -> 99 | PrivateInterfaceSealedClass.SecondObject 100 | } 101 | } 102 | 103 | /** 104 | * The index of [this] in the values list. 105 | */ 106 | public val PrivateInterfaceSealedClass.ordinal: Int 107 | get() = PrivateInterfaceSealedClassSealedEnum.ordinalOf(this) 108 | 109 | /** 110 | * The name of [this] for use with valueOf. 111 | */ 112 | public val PrivateInterfaceSealedClass.name: String 113 | get() = PrivateInterfaceSealedClassSealedEnum.nameOf(this) 114 | 115 | /** 116 | * A list of all [PrivateInterfaceSealedClass] objects. 117 | */ 118 | public val PrivateInterfaceSealedClass.Companion.values: List 119 | get() = PrivateInterfaceSealedClassSealedEnum.values 120 | 121 | /** 122 | * Returns an implementation of [SealedEnum] for the sealed class [PrivateInterfaceSealedClass] 123 | */ 124 | public val PrivateInterfaceSealedClass.Companion.sealedEnum: PrivateInterfaceSealedClassSealedEnum 125 | get() = PrivateInterfaceSealedClassSealedEnum 126 | 127 | /** 128 | * Returns the [PrivateInterfaceSealedClass] object for the given [name]. 129 | * 130 | * If the given name doesn't correspond to any [PrivateInterfaceSealedClass], an 131 | * [IllegalArgumentException] will be thrown. 132 | */ 133 | public fun PrivateInterfaceSealedClass.Companion.valueOf(name: String): PrivateInterfaceSealedClass 134 | = PrivateInterfaceSealedClassSealedEnum.valueOf(name) 135 | 136 | """.trimIndent() 137 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/visibility/PrivateInterfaceSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class PrivateInterfaceSealedClassTests { 11 | 12 | @Test 13 | fun `sealed class has correct values`() { 14 | assertEquals( 15 | listOf( 16 | PrivateInterfaceSealedClass.FirstObject, 17 | PrivateInterfaceSealedClass.SecondObject 18 | ), 19 | PrivateInterfaceSealedClass.values 20 | ) 21 | } 22 | 23 | @Test 24 | fun `sealed class has correct enum values`() { 25 | assertEquals( 26 | listOf( 27 | PrivateInterfaceSealedClassEnum.PrivateInterfaceSealedClass_FirstObject, 28 | PrivateInterfaceSealedClassEnum.PrivateInterfaceSealedClass_SecondObject 29 | ), 30 | enumValues().toList() 31 | ) 32 | } 33 | 34 | @Test 35 | fun `sealed class has correct enum values with mapping`() { 36 | assertEquals( 37 | PrivateInterfaceSealedClass.values.map(PrivateInterfaceSealedClass::enum), 38 | enumValues().toList() 39 | ) 40 | } 41 | 42 | @Test 43 | fun `sealed class has correct enum class`() { 44 | assertEquals( 45 | PrivateInterfaceSealedClassEnum::class, 46 | PrivateInterfaceSealedClass.sealedEnum.enumClass 47 | ) 48 | } 49 | 50 | @Test 51 | fun `compilation generates correct code`() { 52 | val result = compile( 53 | getCommonSourceFile("compilation", "visibility", "PrivateInterfaceSealedClass.kt") 54 | ) 55 | 56 | assertCompiles(result) 57 | assertGeneratedFileMatches( 58 | "PrivateInterfaceSealedClass_SealedEnum.kt", 59 | privateInterfaceSealedClassGenerated, 60 | result 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/visibility/ProtectedInterfaceSealedClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Assertions.assertTrue 9 | import org.junit.jupiter.api.Test 10 | import org.junit.jupiter.api.condition.DisabledIf 11 | import kotlin.reflect.full.isSubclassOf 12 | 13 | class ProtectedInterfaceSealedClassTests { 14 | 15 | @Test 16 | fun `sealed class has correct values`() { 17 | assertEquals( 18 | listOf( 19 | ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass.FirstObject, 20 | ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass.SecondObject 21 | ), 22 | ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass.values 23 | ) 24 | } 25 | 26 | @Test 27 | fun `sealed class implements interface`() { 28 | assertTrue( 29 | ProtectedInterfaceOuterClass_ProtectedInterfaceSealedClassEnum::class.isSubclassOf( 30 | JavaProtectedInterfaceBaseClass.ProtectedInterface::class 31 | ) 32 | ) 33 | } 34 | 35 | @Test 36 | fun `sealed class has correct enum values`() { 37 | assertEquals( 38 | listOf( 39 | ProtectedInterfaceOuterClass_ProtectedInterfaceSealedClassEnum.ProtectedInterfaceOuterClass_ProtectedInterfaceSealedClass_FirstObject, 40 | ProtectedInterfaceOuterClass_ProtectedInterfaceSealedClassEnum.ProtectedInterfaceOuterClass_ProtectedInterfaceSealedClass_SecondObject 41 | ), 42 | enumValues().toList() 43 | ) 44 | } 45 | 46 | @Test 47 | fun `sealed class has correct enum values with mapping`() { 48 | assertEquals( 49 | ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass.values.map( 50 | ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass::enum 51 | ), 52 | enumValues().toList() 53 | ) 54 | } 55 | 56 | @Test 57 | fun `sealed class has correct enum class`() { 58 | assertEquals( 59 | ProtectedInterfaceOuterClass_ProtectedInterfaceSealedClassEnum::class, 60 | ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass.sealedEnum.enumClass 61 | ) 62 | } 63 | 64 | /** 65 | * TODO: In kotlin-compile-testing, the enum doesn't implement the interface, even though it does when run with ksp 66 | * directly (see above) 67 | */ 68 | @Test 69 | @DisabledIf("com.livefront.sealedenum.testing.ProcessingTypeGetter#isKSP") 70 | fun `compilation generates correct code`() { 71 | val result = compile( 72 | getCommonSourceFile("compilation", "visibility", "ProtectedInterfaceSealedClass.kt") 73 | ) 74 | 75 | assertCompiles(result) 76 | assertGeneratedFileMatches( 77 | "ProtectedInterfaceOuterClass.ProtectedInterfaceSealedClass_SealedEnum.kt", 78 | protectedInterfaceSealedClassGenerated, 79 | result 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/compilation/visibility/ProtectedInterfaceSealedClassWithDifferentPackageBaseClassTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.visibility 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getCommonSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | class ProtectedInterfaceSealedClassWithDifferentPackageBaseClassTests { 11 | 12 | @Test 13 | fun `sealed class has correct values`() { 14 | assertEquals( 15 | listOf( 16 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass.FirstObject, 17 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass.SecondObject 18 | ), 19 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass.values 20 | ) 21 | } 22 | 23 | @Test 24 | fun `sealed class has correct enum values`() { 25 | assertEquals( 26 | listOf( 27 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass_ProtectedInterfaceSealedClassEnum.ProtectedInterfaceOuterClassWithDifferentPackageBaseClass_ProtectedInterfaceSealedClass_FirstObject, 28 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass_ProtectedInterfaceSealedClassEnum.ProtectedInterfaceOuterClassWithDifferentPackageBaseClass_ProtectedInterfaceSealedClass_SecondObject 29 | ), 30 | enumValues().toList() 31 | ) 32 | } 33 | 34 | @Test 35 | fun `sealed class has correct enum values with mapping`() { 36 | assertEquals( 37 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass.values.map( 38 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass::enum 39 | ), 40 | enumValues().toList() 41 | ) 42 | } 43 | 44 | @Test 45 | fun `sealed class has correct enum class`() { 46 | assertEquals( 47 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass_ProtectedInterfaceSealedClassEnum::class, 48 | ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass.sealedEnum.enumClass 49 | ) 50 | } 51 | 52 | @Test 53 | fun `compilation generates correct code`() { 54 | val result = compile( 55 | getCommonSourceFile( 56 | "compilation", 57 | "visibility", 58 | "ProtectedInterfaceSealedClassWithDifferentPackageBaseClass.kt" 59 | ) 60 | ) 61 | 62 | assertCompiles(result) 63 | assertGeneratedFileMatches( 64 | "ProtectedInterfaceOuterClassWithDifferentPackageBaseClass.ProtectedInterfaceSealedClass_SealedEnum.kt", 65 | protectedInterfaceSealedClassWithDifferentPackageBaseClassGenerated, 66 | result 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/errors/NonObjectErrors.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.errors 2 | 3 | import com.livefront.sealedenum.testing.assertFails 4 | import com.livefront.sealedenum.testing.compile 5 | import com.tschuchort.compiletesting.SourceFile 6 | import org.intellij.lang.annotations.Language 7 | import org.junit.jupiter.api.Assertions.assertTrue 8 | import org.junit.jupiter.api.Test 9 | 10 | class NonObjectErrors { 11 | 12 | @Test 13 | fun `direct child fails with non object error`() { 14 | @Language("kotlin") 15 | val source = """ 16 | import com.livefront.sealedenum.GenSealedEnum 17 | 18 | sealed class NonObjectSealedClass { 19 | 20 | class NonObject : NonObjectSealedClass() 21 | 22 | @GenSealedEnum 23 | companion object 24 | } 25 | """.trimIndent() 26 | 27 | val result = compile(SourceFile.kotlin("NonObjectSealedClass.kt", source)) 28 | 29 | assertFails(result) 30 | assertTrue(result.messages.contains("Annotated sealed class has a non-object subclass")) 31 | } 32 | 33 | @Test 34 | fun `first level child fails with non object error`() { 35 | @Language("kotlin") 36 | val source = """ 37 | import com.livefront.sealedenum.GenSealedEnum 38 | 39 | sealed class NonObjectSealedClass { 40 | 41 | object FirstObject : NonObjectSealedClass() 42 | 43 | sealed class InnerSealedClass : NonObjectSealedClass() { 44 | data class NonObject(val unit: Unit) : InnerSealedClass() 45 | } 46 | 47 | @GenSealedEnum 48 | companion object 49 | } 50 | """.trimIndent() 51 | 52 | val result = compile(SourceFile.kotlin("NonObjectSealedClass.kt", source)) 53 | 54 | assertFails(result) 55 | assertTrue(result.messages.contains("Annotated sealed class has a non-object subclass")) 56 | } 57 | 58 | @Test 59 | fun `second level child fails with non object error`() { 60 | @Language("kotlin") 61 | val source = """ 62 | import com.livefront.sealedenum.GenSealedEnum 63 | 64 | sealed class NonObjectSealedClass { 65 | 66 | object FirstObject : NonObjectSealedClass() 67 | 68 | sealed class FirstInnerSealedClass : NonObjectSealedClass() { 69 | 70 | object SecondObject : FirstInnerSealedClass() 71 | 72 | sealed class SecondInnerSealedClass : FirstInnerSealedClass() { 73 | class NonObject(val unit: Unit) : SecondInnerSealedClass() 74 | } 75 | } 76 | 77 | @GenSealedEnum 78 | companion object 79 | } 80 | """.trimIndent() 81 | 82 | val result = compile(SourceFile.kotlin("NonObjectSealedClass.kt", source)) 83 | 84 | assertFails(result) 85 | assertTrue(result.messages.contains("Annotated sealed class has a non-object subclass")) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/testing/CompilationAssertions.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | import com.tschuchort.compiletesting.KotlinCompilation 4 | import org.intellij.lang.annotations.Language 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | 7 | /** 8 | * Asserts that the given [result] compiled successfully. 9 | */ 10 | internal fun assertCompiles(result: KotlinCompilation.Result) { 11 | assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) 12 | } 13 | 14 | /** 15 | * Asserts that the given [result] failed to compile. 16 | */ 17 | internal fun assertFails(result: KotlinCompilation.Result) { 18 | assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) 19 | } 20 | 21 | /** 22 | * Asserts that the contents of the generated source file with the name of [fileName] is equal to [expected] for the 23 | * given [result] of compilation. 24 | */ 25 | internal fun assertGeneratedFileMatches( 26 | fileName: String, 27 | @Language("kotlin") expected: String, 28 | result: KotlinCompilation.Result 29 | ) { 30 | assertEquals( 31 | expected, 32 | result.outputDirectory.parentFile.walk().first { it.name == fileName }.readText() 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/testing/PathsUtils.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | import com.tschuchort.compiletesting.SourceFile 4 | import java.nio.file.Paths 5 | 6 | /** 7 | * Returns the common [SourceFile] for the given [paths] calculated relative to [com.livefront.sealedenum] in the test 8 | * folder. 9 | */ 10 | internal fun getCommonSourceFile(vararg paths: String): SourceFile = SourceFile.fromPath( 11 | Paths.get( 12 | "..", 13 | "common", 14 | "test", 15 | if (paths.last().endsWith(".java")) "java" else "kotlin", 16 | "com", 17 | "livefront", 18 | "sealedenum", 19 | *paths 20 | ).toFile() 21 | ) 22 | 23 | /** 24 | * Returns the unique [SourceFile] for the given [paths] calculated relative to [com.livefront.sealedenum] in the test 25 | * folder. 26 | */ 27 | internal fun getSourceFile(vararg paths: String): SourceFile = SourceFile.fromPath( 28 | Paths.get( 29 | "src", 30 | "test", 31 | if (paths.last().endsWith(".java")) "java" else "kotlin", 32 | "com", 33 | "livefront", 34 | "sealedenum", 35 | *paths 36 | ).toFile() 37 | ) 38 | -------------------------------------------------------------------------------- /processing-tests/common/test/kotlin/com/livefront/sealedenum/testing/ProcessingType.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | 5 | sealed class ProcessingType { 6 | 7 | object AnnotationProcessing : ProcessingType() 8 | 9 | object KotlinSymbolProcessing : ProcessingType() 10 | 11 | @GenSealedEnum(generateEnum = true) 12 | companion object 13 | } 14 | 15 | object ProcessingTypeGetter { 16 | @JvmStatic 17 | fun isKSP(): Boolean = ProcessingType.currentType is ProcessingType.KotlinSymbolProcessing 18 | } 19 | -------------------------------------------------------------------------------- /processing-tests/ksp-tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.livefront.sealedenum.kotlin") 3 | id("com.livefront.sealedenum.detekt") 4 | alias(libs.plugins.ksp) 5 | } 6 | 7 | /** 8 | * Swap to `true` to allow debugging `processor-tests` 9 | */ 10 | val debugProcessor = false 11 | if (!debugProcessor) { 12 | sourceSets { 13 | test { 14 | java { 15 | srcDir("$rootDir/processing-tests/common/test/java") 16 | srcDir("$rootDir/processing-tests/common/test/kotlin") 17 | } 18 | } 19 | } 20 | } 21 | 22 | detekt { 23 | source = files( 24 | "src/main/java", 25 | "src/test/java", 26 | "src/main/kotlin", 27 | "src/test/kotlin", 28 | "$rootDir/processing-tests/common/test/java", 29 | "$rootDir/processing-tests/common/test/kotlin" 30 | ) 31 | } 32 | 33 | dependencies { 34 | testImplementation(libs.junit.jupiter) 35 | testImplementation(libs.kotlinCompileTesting.base) 36 | testImplementation(libs.kotlinCompileTesting.ksp) 37 | testImplementation(kotlin("reflect")) 38 | testImplementation(projects.runtime) 39 | testImplementation(libs.ksp.runtime) 40 | testImplementation(libs.ksp.api) 41 | testImplementation(projects.ksp) 42 | kspTest(projects.ksp) 43 | } 44 | -------------------------------------------------------------------------------- /processing-tests/ksp-tests/src/test/kotlin/com/livefront/sealedenum/compilation/ksp/NestedObjectsWithSameName.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.ksp 2 | 3 | import com.livefront.sealedenum.GenSealedEnum 4 | import org.intellij.lang.annotations.Language 5 | 6 | @Suppress("UtilityClassWithPublicConstructor") 7 | class NestedObjectsWithSameName { 8 | companion object { 9 | sealed class EmptySealedClass { 10 | @GenSealedEnum(generateEnum = true) 11 | companion object 12 | } 13 | } 14 | } 15 | 16 | @Language("kotlin") 17 | val nestedObjectsWithSameNameEmptySealedClassGenerated = """ 18 | package com.livefront.sealedenum.compilation.ksp 19 | 20 | import com.livefront.sealedenum.EnumForSealedEnumProvider 21 | import com.livefront.sealedenum.SealedEnum 22 | import com.livefront.sealedenum.SealedEnumWithEnumProvider 23 | import kotlin.Int 24 | import kotlin.LazyThreadSafetyMode 25 | import kotlin.String 26 | import kotlin.collections.List 27 | import kotlin.reflect.KClass 28 | 29 | /** 30 | * An isomorphic enum for the sealed class [NestedObjectsWithSameName.Companion.EmptySealedClass] 31 | */ 32 | public enum class NestedObjectsWithSameName_Companion_EmptySealedClassEnum() 33 | 34 | /** 35 | * The isomorphic [NestedObjectsWithSameName_Companion_EmptySealedClassEnum] for [this]. 36 | */ 37 | public val NestedObjectsWithSameName.Companion.EmptySealedClass.`enum`: 38 | NestedObjectsWithSameName_Companion_EmptySealedClassEnum 39 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum.sealedObjectToEnum(this) 40 | 41 | /** 42 | * The isomorphic [NestedObjectsWithSameName.Companion.EmptySealedClass] for [this]. 43 | */ 44 | public val NestedObjectsWithSameName_Companion_EmptySealedClassEnum.sealedObject: 45 | NestedObjectsWithSameName.Companion.EmptySealedClass 46 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum.enumToSealedObject(this) 47 | 48 | /** 49 | * An implementation of [SealedEnum] for the sealed class 50 | * [NestedObjectsWithSameName.Companion.EmptySealedClass] 51 | */ 52 | public object NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum : 53 | SealedEnum, 54 | SealedEnumWithEnumProvider, 55 | EnumForSealedEnumProvider 56 | { 57 | public override val values: List by 58 | lazy(mode = LazyThreadSafetyMode.PUBLICATION) { 59 | emptyList() 60 | } 61 | 62 | 63 | public override val enumClass: KClass 64 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassEnum::class 65 | 66 | public override fun ordinalOf(obj: NestedObjectsWithSameName.Companion.EmptySealedClass): Int = 67 | throw 68 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 69 | 70 | public override fun nameOf(obj: NestedObjectsWithSameName.Companion.EmptySealedClass): String = 71 | throw 72 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 73 | 74 | public override fun valueOf(name: String): NestedObjectsWithSameName.Companion.EmptySealedClass 75 | = throw IllegalArgumentException(""${'"'}No sealed enum constant ${'$'}name""${'"'}) 76 | 77 | public override 78 | fun sealedObjectToEnum(obj: NestedObjectsWithSameName.Companion.EmptySealedClass): 79 | NestedObjectsWithSameName_Companion_EmptySealedClassEnum = throw 80 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 81 | 82 | public override 83 | fun enumToSealedObject(`enum`: NestedObjectsWithSameName_Companion_EmptySealedClassEnum): 84 | NestedObjectsWithSameName.Companion.EmptySealedClass = throw 85 | AssertionError("Constructing a EmptySealedClass is impossible, since it has no sealed subclasses") 86 | } 87 | 88 | /** 89 | * The index of [this] in the values list. 90 | */ 91 | public val NestedObjectsWithSameName.Companion.EmptySealedClass.ordinal: Int 92 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum.ordinalOf(this) 93 | 94 | /** 95 | * The name of [this] for use with valueOf. 96 | */ 97 | public val NestedObjectsWithSameName.Companion.EmptySealedClass.name: String 98 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum.nameOf(this) 99 | 100 | /** 101 | * A list of all [NestedObjectsWithSameName.Companion.EmptySealedClass] objects. 102 | */ 103 | public val NestedObjectsWithSameName.Companion.EmptySealedClass.Companion.values: 104 | List 105 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum.values 106 | 107 | /** 108 | * Returns an implementation of [SealedEnum] for the sealed class 109 | * [NestedObjectsWithSameName.Companion.EmptySealedClass] 110 | */ 111 | public val NestedObjectsWithSameName.Companion.EmptySealedClass.Companion.sealedEnum: 112 | NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum 113 | get() = NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum 114 | 115 | /** 116 | * Returns the [NestedObjectsWithSameName.Companion.EmptySealedClass] object for the given [name]. 117 | * 118 | * If the given name doesn't correspond to any 119 | * [NestedObjectsWithSameName.Companion.EmptySealedClass], an [IllegalArgumentException] will be 120 | * thrown. 121 | */ 122 | public fun NestedObjectsWithSameName.Companion.EmptySealedClass.Companion.valueOf(name: String): 123 | NestedObjectsWithSameName.Companion.EmptySealedClass = 124 | NestedObjectsWithSameName_Companion_EmptySealedClassSealedEnum.valueOf(name) 125 | 126 | """.trimIndent() 127 | -------------------------------------------------------------------------------- /processing-tests/ksp-tests/src/test/kotlin/com/livefront/sealedenum/compilation/ksp/NestedObjectsWithSameNameTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.ksp 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Test 9 | 10 | /** 11 | * Verifies that a sealed class in a companion object (or more precisely, an annotation that appears on a nested object 12 | * with the same name) still generates a sealed enum. 13 | * 14 | * Note that this only works with KSP, due to a limitation with KAPT. 15 | * Because the generated stubs for such an object wouldn't be valid Java, they are skipped, meaning that the annotation 16 | * wouldn't be processed. 17 | * 18 | * @see sealed-enum/issues/60 19 | */ 20 | class NestedObjectsWithSameNameTests { 21 | @Test 22 | fun `empty sealed class`() { 23 | assertEquals( 24 | emptyList(), 25 | NestedObjectsWithSameName.Companion.EmptySealedClass.values 26 | ) 27 | } 28 | 29 | @Test 30 | fun `empty enum for sealed class`() { 31 | assertEquals( 32 | NestedObjectsWithSameName.Companion.EmptySealedClass.values.map( 33 | NestedObjectsWithSameName.Companion.EmptySealedClass::enum 34 | ), 35 | enumValues().toList() 36 | ) 37 | } 38 | 39 | @Test 40 | fun `correct enum class`() { 41 | assertEquals( 42 | NestedObjectsWithSameName_Companion_EmptySealedClassEnum::class, 43 | NestedObjectsWithSameName.Companion.EmptySealedClass.sealedEnum.enumClass 44 | ) 45 | } 46 | 47 | @Test 48 | fun `compilation generates correct code`() { 49 | val result = compile(getSourceFile("compilation", "ksp", "NestedObjectsWithSameName.kt")) 50 | 51 | assertCompiles(result) 52 | assertGeneratedFileMatches( 53 | "NestedObjectsWithSameName.Companion.EmptySealedClass_SealedEnum.kt", 54 | nestedObjectsWithSameNameEmptySealedClassGenerated, 55 | result 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /processing-tests/ksp-tests/src/test/kotlin/com/livefront/sealedenum/testing/Compilation.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | import com.livefront.sealedenum.internal.ksp.SealedEnumProcessorProvider 4 | import com.tschuchort.compiletesting.KotlinCompilation 5 | import com.tschuchort.compiletesting.SourceFile 6 | import com.tschuchort.compiletesting.symbolProcessorProviders 7 | 8 | /** 9 | * Compiles the given [sourceFiles] with the application of [SealedEnumProcessorProvider]. 10 | */ 11 | internal fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result = 12 | KotlinCompilation().apply { 13 | sources = sourceFiles.toList() 14 | symbolProcessorProviders = listOf(SealedEnumProcessorProvider()) 15 | inheritClassPath = true 16 | }.compile() 17 | -------------------------------------------------------------------------------- /processing-tests/ksp-tests/src/test/kotlin/com/livefront/sealedenum/testing/CurrentProcessingType.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | /** 4 | * Returns the current processing type. 5 | */ 6 | val ProcessingType.Companion.currentType get(): ProcessingType = ProcessingType.KotlinSymbolProcessing 7 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.livefront.sealedenum.kotlin") 3 | id("com.livefront.sealedenum.detekt") 4 | kotlin("kapt") 5 | } 6 | 7 | /** 8 | * Swap to `true` to allow debugging `ksp-tests` 9 | */ 10 | val debugKsp = false 11 | if (!debugKsp) { 12 | sourceSets { 13 | test { 14 | java { 15 | srcDir("$rootDir/processing-tests/common/test/java") 16 | srcDir("$rootDir/processing-tests/common/test/kotlin") 17 | } 18 | } 19 | } 20 | } 21 | 22 | detekt { 23 | source = files( 24 | "src/main/java", 25 | "src/test/java", 26 | "src/main/kotlin", 27 | "src/test/kotlin", 28 | "$rootDir/processing-tests/common/test/java", 29 | "$rootDir/processing-tests/common/test/kotlin" 30 | ) 31 | } 32 | 33 | dependencies { 34 | testImplementation(libs.junit.jupiter) 35 | testImplementation(libs.kotlinCompileTesting.base) 36 | testImplementation(kotlin("reflect")) 37 | testImplementation(projects.runtime) 38 | testImplementation(projects.processor) 39 | kaptTest(projects.processor) 40 | } 41 | 42 | // See https://github.com/tschuchortdev/kotlin-compile-testing#java-16-compatibility 43 | if (JavaVersion.current() >= JavaVersion.VERSION_16) { 44 | tasks.withType().all { 45 | jvmArgs( 46 | "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", 47 | "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", 48 | "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", 49 | "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", 50 | "--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", 51 | "--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", 52 | "--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", 53 | "--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", 54 | "--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", 55 | "--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaFirstBaseClass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | import java.util.Collection; 4 | 5 | public class JavaFirstBaseClass> extends KotlinFirstBaseClass implements KotlinInterface2, JavaInterface1 { 6 | } 7 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaInterface1.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | public interface JavaInterface1 { 4 | } 5 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaInterface2.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | public interface JavaInterface2 { 4 | } 5 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaInterface3.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | interface JavaInterface3 { 4 | } 5 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaInterface4.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | interface JavaInterface4 { 4 | } 5 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaInterface5.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | import java.util.Collection; 4 | 5 | public interface JavaInterface5 { 6 | } 7 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/java/com/livefront/sealedenum/compilation/kitchensink/JavaSecondBaseClass.java: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink; 2 | 3 | import java.util.Collection; 4 | 5 | public class JavaSecondBaseClass> 6 | extends KotlinSecondBaseClass> 7 | implements KotlinInterface5, JavaInterface3 { 8 | } 9 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/kotlin/com/livefront/sealedenum/compilation/kitchensink/JavaBaseClassesTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.compilation.kitchensink 2 | 3 | import com.livefront.sealedenum.testing.assertCompiles 4 | import com.livefront.sealedenum.testing.assertGeneratedFileMatches 5 | import com.livefront.sealedenum.testing.compile 6 | import com.livefront.sealedenum.testing.getSourceFile 7 | import org.junit.jupiter.api.Assertions.assertEquals 8 | import org.junit.jupiter.api.Assertions.assertTrue 9 | import org.junit.jupiter.api.Test 10 | import kotlin.reflect.KTypeProjection 11 | import kotlin.reflect.full.createType 12 | import kotlin.reflect.full.isSubtypeOf 13 | 14 | class JavaBaseClassesTests { 15 | 16 | @Suppress("LongMethod") 17 | @Test 18 | fun `enum implements correct interfaces with type arguments`() { 19 | assertTrue( 20 | JavaBaseClassesSealedClassEnum::class.createType().isSubtypeOf( 21 | KotlinInterface1::class.createType() 22 | ) 23 | ) 24 | 25 | assertTrue( 26 | JavaBaseClassesSealedClassEnum::class.createType().isSubtypeOf( 27 | KotlinInterface2::class.createType( 28 | arguments = listOf( 29 | KTypeProjection.invariant(String::class.createType()) 30 | ) 31 | ) 32 | ) 33 | ) 34 | 35 | assertTrue( 36 | JavaBaseClassesSealedClassEnum::class.createType().isSubtypeOf( 37 | KotlinInterface3::class.createType( 38 | arguments = listOf( 39 | KTypeProjection.invariant( 40 | List::class.createType( 41 | arguments = listOf( 42 | KTypeProjection.invariant(String::class.createType()) 43 | ) 44 | ) 45 | ) 46 | ) 47 | ) 48 | ) 49 | ) 50 | 51 | assertTrue( 52 | JavaBaseClassesSealedClassEnum::class.createType().isSubtypeOf( 53 | KotlinInterface4::class.createType( 54 | arguments = listOf( 55 | KTypeProjection.invariant(Double::class.createType()) 56 | ) 57 | ) 58 | ) 59 | ) 60 | 61 | assertTrue( 62 | JavaBaseClassesSealedClassEnum::class.createType().isSubtypeOf( 63 | KotlinInterface5::class.createType( 64 | arguments = listOf( 65 | KTypeProjection.invariant(KotlinInterface1::class.createType()) 66 | ) 67 | ) 68 | ) 69 | ) 70 | 71 | assertTrue( 72 | JavaBaseClassesSealedClassEnum::class.createType().isSubtypeOf( 73 | KotlinInterface6::class.createType( 74 | arguments = listOf( 75 | KTypeProjection.invariant(Int::class.createType()) 76 | ) 77 | ) 78 | ) 79 | ) 80 | 81 | // Check for compilation 82 | val values1: Array = JavaBaseClassesSealedClassEnum.values() 83 | val values2: Array> = JavaBaseClassesSealedClassEnum.values() 84 | val values3: Array>> = JavaBaseClassesSealedClassEnum.values() 85 | val values4: Array> = JavaBaseClassesSealedClassEnum.values() 86 | val values5: Array> = 87 | JavaBaseClassesSealedClassEnum.values() 88 | val values6: Array> = JavaBaseClassesSealedClassEnum.values() 89 | val values7: Array = JavaBaseClassesSealedClassEnum.values() 90 | val values8: Array>> = JavaBaseClassesSealedClassEnum.values() 91 | val values9: Array> = JavaBaseClassesSealedClassEnum.values() 92 | val values10: Array = JavaBaseClassesSealedClassEnum.values() 93 | val values11: Array>>>>>> = JavaBaseClassesSealedClassEnum.values() 94 | 95 | assertEquals( 96 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 97 | values1.toList() 98 | ) 99 | 100 | assertEquals( 101 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 102 | values2.toList() 103 | ) 104 | 105 | assertEquals( 106 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 107 | values3.toList() 108 | ) 109 | 110 | assertEquals( 111 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 112 | values4.toList() 113 | ) 114 | 115 | assertEquals( 116 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 117 | values5.toList() 118 | ) 119 | 120 | assertEquals( 121 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 122 | values6.toList() 123 | ) 124 | 125 | assertEquals( 126 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 127 | values7.toList() 128 | ) 129 | 130 | assertEquals( 131 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 132 | values8.toList() 133 | ) 134 | 135 | assertEquals( 136 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 137 | values9.toList() 138 | ) 139 | 140 | assertEquals( 141 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 142 | values10.toList() 143 | ) 144 | 145 | assertEquals( 146 | JavaBaseClassesSealedClass.values.map(JavaBaseClassesSealedClass<*>::enum), 147 | values11.toList() 148 | ) 149 | } 150 | 151 | @Test 152 | fun `compilation generates correct code`() { 153 | val result = compile( 154 | getSourceFile("compilation", "kitchensink", "JavaInterface1.java"), 155 | getSourceFile("compilation", "kitchensink", "JavaInterface2.java"), 156 | getSourceFile("compilation", "kitchensink", "JavaInterface3.java"), 157 | getSourceFile("compilation", "kitchensink", "JavaInterface4.java"), 158 | getSourceFile("compilation", "kitchensink", "JavaInterface5.java"), 159 | getSourceFile("compilation", "kitchensink", "JavaFirstBaseClass.java"), 160 | getSourceFile("compilation", "kitchensink", "JavaSecondBaseClass.java"), 161 | getSourceFile("compilation", "kitchensink", "JavaBaseClasses.kt") 162 | ) 163 | 164 | assertCompiles(result) 165 | assertGeneratedFileMatches( 166 | "JavaBaseClassesSealedClass_SealedEnum.kt", 167 | javaBaseClassesSealedClassGenerated, 168 | result 169 | ) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/kotlin/com/livefront/sealedenum/testing/Compilation.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | import com.livefront.sealedenum.internal.processor.SealedEnumProcessor 4 | import com.tschuchort.compiletesting.KotlinCompilation 5 | import com.tschuchort.compiletesting.SourceFile 6 | 7 | /** 8 | * Compiles the given [sourceFiles] with the application of [SealedEnumProcessor]. 9 | */ 10 | internal fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result = 11 | KotlinCompilation().apply { 12 | sources = sourceFiles.toList() 13 | annotationProcessors = listOf(SealedEnumProcessor()) 14 | inheritClassPath = true 15 | kotlincArguments = listOf("-language-version", "1.5") 16 | }.compile() 17 | -------------------------------------------------------------------------------- /processing-tests/processor-tests/src/test/kotlin/com/livefront/sealedenum/testing/CurrentProcessingType.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.testing 2 | 3 | /** 4 | * Returns the current processing type. 5 | */ 6 | val ProcessingType.Companion.currentType get(): ProcessingType = ProcessingType.AnnotationProcessing 7 | -------------------------------------------------------------------------------- /processor/api/processor.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livefront/sealed-enum/71a0c5c445b3b86d2f3d6e15d070784183f40dc0/processor/api/processor.api -------------------------------------------------------------------------------- /processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.livefront.sealedenum.kotlin") 3 | id("com.livefront.sealedenum.detekt") 4 | id("com.livefront.sealedenum.publish") 5 | kotlin("kapt") 6 | } 7 | 8 | dependencies { 9 | implementation(projects.runtime) 10 | implementation(projects.processingCommon) 11 | implementation(libs.squareUp.kotlinPoet) 12 | implementation(libs.squareUp.kotlinPoetMetadata) 13 | implementation(libs.autoService.runtime) 14 | kapt(libs.autoService.processor) 15 | implementation(libs.incap.runtime) 16 | kapt(libs.incap.processor) 17 | } 18 | 19 | kapt { 20 | includeCompileClasspath = false 21 | } 22 | 23 | tasks.withType().configureEach { 24 | kotlinOptions.freeCompilerArgs += "-opt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview" 25 | } 26 | -------------------------------------------------------------------------------- /processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/ClassInspectorUtil.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.processor 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import kotlinx.metadata.isLocal 5 | 6 | /** 7 | * Converts a [kotlinx.metadata.ClassName] to a [com.squareup.kotlinpoet.ClassName]. 8 | */ 9 | internal fun createClassName(kotlinMetadataName: kotlinx.metadata.ClassName): ClassName { 10 | require(!kotlinMetadataName.isLocal) { 11 | "Local/anonymous classes are not supported!" 12 | } 13 | val simpleNames = kotlinMetadataName.substringAfterLast( 14 | delimiter = '/' 15 | ) 16 | .split(".") 17 | val packageName = kotlinMetadataName.substringBeforeLast( 18 | delimiter = '/', 19 | missingDelimiterValue = "" 20 | ) 21 | 22 | return ClassName( 23 | packageName = packageName.replace("/", "."), 24 | simpleNames = simpleNames 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/InvalidSubclassVisibilityException.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.processor 2 | 3 | import javax.lang.model.element.Element 4 | 5 | /** 6 | * An exception that indicates that a sealed subclass has an invalid visibility. 7 | */ 8 | public class InvalidSubclassVisibilityException(public val element: Element) : Exception() 9 | -------------------------------------------------------------------------------- /processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/NonObjectSealedSubclassException.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.processor 2 | 3 | import javax.lang.model.element.Element 4 | 5 | /** 6 | * An exception that indicates that sealed class has a non-object subclass. 7 | */ 8 | public class NonObjectSealedSubclassException(public val element: Element) : Exception() 9 | -------------------------------------------------------------------------------- /processor/src/main/kotlin/com/livefront/sealedenum/internal/processor/WildcardedTypeParameters.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum.internal.processor 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.STAR 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | 8 | /** 9 | * A mapping of [TypeSpec.typeVariables] to [TypeName]s. 10 | * 11 | * Rather than returning variable names, they are converted into a [TypeName] that can be directly specified in an 12 | * interface. 13 | * 14 | * In particular, single invariant types can be replaced with the type directly. In all other cases (multiple bounds, 15 | * other types of variance) the only thing we can do is wildcard the type. 16 | */ 17 | internal val TypeSpec.wildcardedTypeVariables: List 18 | get() = typeVariables 19 | .map { 20 | if (it.bounds.size == 1) { 21 | when (it.variance) { 22 | KModifier.IN -> STAR 23 | KModifier.OUT -> STAR 24 | null -> it.bounds[0] 25 | else -> throw AssertionError("Invalid variance!") 26 | } 27 | } else { 28 | STAR 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /runtime/api/runtime.api: -------------------------------------------------------------------------------- 1 | public abstract interface class com/livefront/sealedenum/EnumForSealedEnumProvider { 2 | public abstract fun enumToSealedObject (Ljava/lang/Enum;)Ljava/lang/Object; 3 | public abstract fun getEnumClass ()Lkotlin/reflect/KClass; 4 | public abstract fun sealedObjectToEnum (Ljava/lang/Object;)Ljava/lang/Enum; 5 | } 6 | 7 | public abstract interface annotation class com/livefront/sealedenum/GenSealedEnum : java/lang/annotation/Annotation { 8 | public abstract fun generateEnum ()Z 9 | public abstract fun traversalOrder ()Lcom/livefront/sealedenum/TreeTraversalOrder; 10 | } 11 | 12 | public abstract interface annotation class com/livefront/sealedenum/GenSealedEnums : java/lang/annotation/Annotation { 13 | public abstract fun value ()[Lcom/livefront/sealedenum/GenSealedEnum; 14 | } 15 | 16 | public abstract interface class com/livefront/sealedenum/SealedEnum : java/util/Comparator { 17 | public abstract fun compare (Ljava/lang/Object;Ljava/lang/Object;)I 18 | public abstract fun getValues ()Ljava/util/List; 19 | public abstract fun nameOf (Ljava/lang/Object;)Ljava/lang/String; 20 | public abstract fun ordinalOf (Ljava/lang/Object;)I 21 | public abstract fun valueOf (Ljava/lang/String;)Ljava/lang/Object; 22 | } 23 | 24 | public final class com/livefront/sealedenum/SealedEnum$DefaultImpls { 25 | public static fun compare (Lcom/livefront/sealedenum/SealedEnum;Ljava/lang/Object;Ljava/lang/Object;)I 26 | } 27 | 28 | public final class com/livefront/sealedenum/SealedEnumKt { 29 | public static final fun createSealedEnumFromEnumArray ([Ljava/lang/Enum;Lkotlin/reflect/KClass;)Lcom/livefront/sealedenum/SealedEnumWithEnumProvider; 30 | } 31 | 32 | public abstract interface class com/livefront/sealedenum/SealedEnumWithEnumProvider : com/livefront/sealedenum/EnumForSealedEnumProvider, com/livefront/sealedenum/SealedEnum { 33 | } 34 | 35 | public final class com/livefront/sealedenum/SealedEnumWithEnumProvider$DefaultImpls { 36 | public static fun compare (Lcom/livefront/sealedenum/SealedEnumWithEnumProvider;Ljava/lang/Object;Ljava/lang/Object;)I 37 | } 38 | 39 | public final class com/livefront/sealedenum/TreeTraversalOrder : java/lang/Enum { 40 | public static final field IN_ORDER Lcom/livefront/sealedenum/TreeTraversalOrder; 41 | public static final field LEVEL_ORDER Lcom/livefront/sealedenum/TreeTraversalOrder; 42 | public static final field POST_ORDER Lcom/livefront/sealedenum/TreeTraversalOrder; 43 | public static final field PRE_ORDER Lcom/livefront/sealedenum/TreeTraversalOrder; 44 | public static fun valueOf (Ljava/lang/String;)Lcom/livefront/sealedenum/TreeTraversalOrder; 45 | public static fun values ()[Lcom/livefront/sealedenum/TreeTraversalOrder; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /runtime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.livefront.sealedenum.kotlin") 3 | id("com.livefront.sealedenum.detekt") 4 | id("com.livefront.sealedenum.publish") 5 | } 6 | 7 | dependencies { 8 | testImplementation(libs.junit.jupiter) 9 | } 10 | -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/livefront/sealedenum/EnumForSealedEnumProvider.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * Defines a provider for interoperability between a [SealedEnum] for type [T] and a normal enum class [E]. 7 | */ 8 | public interface EnumForSealedEnumProvider> { 9 | /** 10 | * A mapping of the sealed object of type [T] to the enum constant of type [E]. Together with the inverse function 11 | * [enumToSealedObject], this is an isomorphism. 12 | */ 13 | public fun sealedObjectToEnum(obj: T): E 14 | 15 | /** 16 | * A mapping of the enum constant of type [E] to the enum constant of type [T]. Together with the inverse function 17 | * [sealedObjectToEnum], this is an isomorphism. 18 | */ 19 | public fun enumToSealedObject(enum: E): T 20 | 21 | /** 22 | * The class object for the enum. 23 | */ 24 | public val enumClass: KClass 25 | } 26 | -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/livefront/sealedenum/SealedEnum.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * An interface providing `Enum`-like methods in a generic manner, for sealed classes that only have objects as 7 | * subclasses. 8 | * 9 | * Object implementations for [SealedEnum] will automatically be generated by annotating the sealed class's companion 10 | * object with [GenSealedEnum]. Additionally, an implementation of [SealedEnum] can be created for a normal `Enum` with 11 | * [createSealedEnumFromEnum]. 12 | */ 13 | public interface SealedEnum : Comparator { 14 | 15 | /** 16 | * A list of all [T] objects. 17 | */ 18 | public val values: List 19 | 20 | /** 21 | * Returns the index of [obj] in the [values] list. 22 | */ 23 | public fun ordinalOf(obj: T): Int 24 | 25 | /** 26 | * Returns the name of [obj] for use with [valueOf]. 27 | */ 28 | public fun nameOf(obj: T): String 29 | 30 | /** 31 | * Returns the [T] object for the given [name]. 32 | * 33 | * If the given name doesn't correspond to any [T], an [IllegalArgumentException] will be thrown. 34 | */ 35 | public fun valueOf(name: String): T 36 | 37 | public override fun compare(first: T, second: T): Int = ordinalOf(first) - ordinalOf(second) 38 | } 39 | 40 | /** 41 | * Creates a [SealedEnumWithEnumProvider] for the enum [E]. 42 | */ 43 | public inline fun > createSealedEnumFromEnum(): SealedEnumWithEnumProvider = 44 | createSealedEnumFromEnumArray(enumValues(), E::class) 45 | 46 | /** 47 | * Creates a [SealedEnumWithEnumProvider] for the enum [E] with the given array of enum values. 48 | */ 49 | public fun > createSealedEnumFromEnumArray( 50 | values: Array, 51 | enumClass: KClass 52 | ): SealedEnumWithEnumProvider = object : SealedEnumWithEnumProvider { 53 | val nameToValueMap: Map by lazy(LazyThreadSafetyMode.PUBLICATION) { 54 | values.associateBy { it.name } 55 | } 56 | 57 | override val enumClass: KClass = enumClass 58 | 59 | override val values: List = values.toList() 60 | 61 | override fun ordinalOf(obj: E): Int = obj.ordinal 62 | 63 | override fun nameOf(obj: E): String = obj.name 64 | 65 | override fun valueOf(name: String): E = 66 | nameToValueMap[name] ?: throw IllegalArgumentException("No sealed enum constant $name") 67 | 68 | /** 69 | * Isomorphism is given by the identity function 70 | */ 71 | override fun sealedObjectToEnum(obj: E): E = obj 72 | 73 | /** 74 | * Isomorphism is given by the identity function 75 | */ 76 | override fun enumToSealedObject(enum: E): E = enum 77 | } 78 | -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/livefront/sealedenum/SealedEnumWithEnumProvider.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum 2 | 3 | /** 4 | * A [SealedEnum] along with a [EnumForSealedEnumProvider]. 5 | */ 6 | public interface SealedEnumWithEnumProvider> : SealedEnum, EnumForSealedEnumProvider 7 | -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/livefront/sealedenum/TreeTraversalOrder.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum 2 | 3 | /** 4 | * An enum specifying the traversal order for the list of sealed objects. 5 | */ 6 | public enum class TreeTraversalOrder { 7 | /** 8 | * All objects that are direct children of a sealed class will be listed before objects in subclasses that are 9 | * themselves sealed. 10 | */ 11 | PRE_ORDER, 12 | 13 | /** 14 | * Objects will be included in-order, based on the declaration of the sealed class. If all subclasses are 15 | * declared as enclosed elements inside sealed classes, this order will match the the order from top-to-bottom in 16 | * which the objects are declared. 17 | */ 18 | IN_ORDER, 19 | 20 | /** 21 | * All objects that are direct children of a sealed class will be listed after objects in subclasses that are 22 | * themselves sealed. 23 | */ 24 | POST_ORDER, 25 | 26 | /** 27 | * Objects are listed in order based on how deeply they are nested within the sealed subclass. 28 | */ 29 | LEVEL_ORDER 30 | } 31 | -------------------------------------------------------------------------------- /runtime/src/test/kotlin/com/livefront/sealedenum/CreateSealedEnumFromEnumTests.kt: -------------------------------------------------------------------------------- 1 | package com.livefront.sealedenum 2 | 3 | import org.junit.jupiter.api.Assertions.assertEquals 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.assertThrows 6 | import org.junit.jupiter.api.extension.ExtensionContext 7 | import org.junit.jupiter.params.ParameterizedTest 8 | import org.junit.jupiter.params.provider.Arguments 9 | import org.junit.jupiter.params.provider.ArgumentsProvider 10 | import org.junit.jupiter.params.provider.ArgumentsSource 11 | import java.util.stream.Stream 12 | 13 | enum class AlphaEnum { BETA, GAMMA, DELTA } 14 | 15 | class CreateSealedEnumFromEnumTests { 16 | 17 | private val alphaEnumSealedEnum = createSealedEnumFromEnum() 18 | 19 | @Test 20 | fun `verify values are correct`() { 21 | assertEquals(listOf(AlphaEnum.BETA, AlphaEnum.GAMMA, AlphaEnum.DELTA), alphaEnumSealedEnum.values) 22 | } 23 | 24 | @Test 25 | fun `verify ordinal of BETA is correct`() { 26 | assertEquals(0, alphaEnumSealedEnum.ordinalOf(AlphaEnum.BETA)) 27 | } 28 | 29 | @Test 30 | fun `verify ordinal of GAMMA is correct`() { 31 | assertEquals(1, alphaEnumSealedEnum.ordinalOf(AlphaEnum.GAMMA)) 32 | } 33 | 34 | @Test 35 | fun `verify ordinal of DELTA is correct`() { 36 | assertEquals(2, alphaEnumSealedEnum.ordinalOf(AlphaEnum.DELTA)) 37 | } 38 | 39 | data class ComparatorConfig( 40 | val first: AlphaEnum, 41 | val second: AlphaEnum, 42 | val compareValue: Int 43 | ) { 44 | class Provider : ArgumentsProvider { 45 | override fun provideArguments(context: ExtensionContext?): Stream = 46 | listOf( 47 | ComparatorConfig(AlphaEnum.BETA, AlphaEnum.BETA, 0), 48 | ComparatorConfig(AlphaEnum.BETA, AlphaEnum.GAMMA, -1), 49 | ComparatorConfig(AlphaEnum.BETA, AlphaEnum.DELTA, -2), 50 | ComparatorConfig(AlphaEnum.GAMMA, AlphaEnum.BETA, 1), 51 | ComparatorConfig(AlphaEnum.GAMMA, AlphaEnum.GAMMA, 0), 52 | ComparatorConfig(AlphaEnum.GAMMA, AlphaEnum.DELTA, -1), 53 | ComparatorConfig(AlphaEnum.DELTA, AlphaEnum.BETA, 2), 54 | ComparatorConfig(AlphaEnum.DELTA, AlphaEnum.GAMMA, 1), 55 | ComparatorConfig(AlphaEnum.DELTA, AlphaEnum.DELTA, 0) 56 | ).map { Arguments.of(it) }.stream() 57 | } 58 | } 59 | 60 | @ParameterizedTest 61 | @ArgumentsSource(ComparatorConfig.Provider::class) 62 | fun `verify compareTo is correct`(config: ComparatorConfig) { 63 | assertEquals(config.compareValue, alphaEnumSealedEnum.compare(config.first, config.second)) 64 | } 65 | 66 | @Test 67 | fun `verify name of BETA is correct`() { 68 | assertEquals("BETA", alphaEnumSealedEnum.nameOf(AlphaEnum.BETA)) 69 | } 70 | 71 | @Test 72 | fun `verify name of GAMMA is correct`() { 73 | assertEquals("GAMMA", alphaEnumSealedEnum.nameOf(AlphaEnum.GAMMA)) 74 | } 75 | 76 | @Test 77 | fun `verify name of DELTA is correct`() { 78 | assertEquals("DELTA", alphaEnumSealedEnum.nameOf(AlphaEnum.DELTA)) 79 | } 80 | 81 | @Test 82 | fun `verify valueOf of BETA is correct`() { 83 | assertEquals(AlphaEnum.BETA, alphaEnumSealedEnum.valueOf("BETA")) 84 | } 85 | 86 | @Test 87 | fun `verify valueOf of GAMMA is correct`() { 88 | assertEquals(AlphaEnum.GAMMA, alphaEnumSealedEnum.valueOf("GAMMA")) 89 | } 90 | 91 | @Test 92 | fun `verify valueOf of DELTA is correct`() { 93 | assertEquals(AlphaEnum.DELTA, alphaEnumSealedEnum.valueOf("DELTA")) 94 | } 95 | 96 | @Test 97 | fun `verify valueOf of EPSILON throws exception`() { 98 | assertThrows { 99 | alphaEnumSealedEnum.valueOf("EPSILON") 100 | } 101 | } 102 | 103 | @Test 104 | fun `verify enum class is correct`() { 105 | assertEquals(AlphaEnum::class, alphaEnumSealedEnum.enumClass) 106 | } 107 | 108 | @Test 109 | fun `verify sealed enum to object is correct for BETA`() { 110 | assertEquals(AlphaEnum.BETA, alphaEnumSealedEnum.sealedObjectToEnum(AlphaEnum.BETA)) 111 | } 112 | 113 | @Test 114 | fun `verify sealed enum to object is correct for GAMMA`() { 115 | assertEquals(AlphaEnum.GAMMA, alphaEnumSealedEnum.sealedObjectToEnum(AlphaEnum.GAMMA)) 116 | } 117 | 118 | @Test 119 | fun `verify sealed enum to object is correct for DELTA`() { 120 | assertEquals(AlphaEnum.DELTA, alphaEnumSealedEnum.sealedObjectToEnum(AlphaEnum.DELTA)) 121 | } 122 | 123 | @Test 124 | fun `verify object to sealed enum is correct for BETA`() { 125 | assertEquals(AlphaEnum.BETA, alphaEnumSealedEnum.enumToSealedObject(AlphaEnum.BETA)) 126 | } 127 | 128 | @Test 129 | fun `verify object to sealed enum is correct for GAMMA`() { 130 | assertEquals(AlphaEnum.GAMMA, alphaEnumSealedEnum.enumToSealedObject(AlphaEnum.GAMMA)) 131 | } 132 | 133 | @Test 134 | fun `verify object to sealed enum is correct for DELTA`() { 135 | assertEquals(AlphaEnum.DELTA, alphaEnumSealedEnum.enumToSealedObject(AlphaEnum.DELTA)) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | includeBuild("build-logic") 5 | repositories { 6 | mavenCentral() 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | 18 | include(":runtime") 19 | include(":processing-common") 20 | include(":processor") 21 | include(":ksp") 22 | include(":processing-tests:processor-tests") 23 | include(":processing-tests:ksp-tests") 24 | include(":jacoco") 25 | rootProject.name = "sealed-enum" 26 | --------------------------------------------------------------------------------