├── compiler ├── cli │ ├── resources │ │ ├── mockito-extensions │ │ │ └── org.mockito.plugins.MockMaker │ │ └── META-INF │ │ │ └── services │ │ │ ├── org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor │ │ │ └── org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar │ ├── build.gradle.kts │ └── src │ │ ├── test │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ ├── CompilerConfigurationPropertyTest.kt │ │ │ ├── DumpingVisitor.kt │ │ │ ├── DcgTestCase.kt │ │ │ └── DcgCompilerIdentityTest.kt │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ ├── DataClassGenerateComponentRegistrar.kt │ │ └── DataClassGenerateCommandLineProcessor.kt ├── k1 │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ ├── misc │ │ │ ├── IdePsiElementAlias.kt │ │ │ ├── PsiElementAlias.kt │ │ │ ├── AnnotationDescriptorExt.kt │ │ │ ├── ClassLiteralExt.kt │ │ │ └── JavaDeclarationOriginExt.kt │ │ │ ├── ModeExt.kt │ │ │ ├── visitor │ │ │ ├── SuperClassInitCallOverrideVisitor.kt │ │ │ └── Visitors.kt │ │ │ ├── DataClassGenerateInterceptorExtension.kt │ │ │ └── DataClassGenerateBuilder.kt │ └── build.gradle.kts ├── common │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── configuration │ │ ├── DataClassGenerateExt.kt │ │ ├── CompilerConfigurationProperty.kt │ │ ├── PluginMode.kt │ │ └── CompilerConfigurationProperties.kt └── k2 │ └── build.gradle.kts ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── gradleplugin ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── settings.gradle.kts ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── gradle │ │ ├── DataClassGeneratePluginExtension.kt │ │ └── DataClassGeneratePlugin.kt ├── build.gradle.kts ├── gradlew.bat └── gradlew ├── examples ├── strict │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── facebook │ │ │ │ └── kotlin │ │ │ │ └── compilerplugins │ │ │ │ └── dataclassgenerate │ │ │ │ └── examples │ │ │ │ ├── ParcelizeDataClass.kt │ │ │ │ ├── SerializableDataClass.kt │ │ │ │ └── SampleDataClass.kt │ │ └── test │ │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ └── examples │ │ │ └── SerializableDataClassTest.kt │ └── build.gradle.kts ├── innerclass │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ └── examples │ │ │ ├── WrappingInterface.kt │ │ │ └── SuperWrapper.kt │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── examples │ │ └── InnerDataClassTest.kt ├── optout │ ├── build.gradle.kts │ └── src │ │ ├── test │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ └── examples │ │ │ └── DataClassGenerateOptOutTest.kt │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── examples │ │ └── SampleDataClass.kt ├── println │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── sample │ │ └── DataClassGenerateSample.kt ├── explicit │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ └── examples │ │ │ └── SampleDataClass.kt │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── examples │ │ └── DataClassGenerateTest.kt ├── implicit │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ └── examples │ │ │ └── SampleDataClass.kt │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── examples │ │ └── DataClassGenerateTest.kt ├── superclass │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── facebook │ │ │ └── kotlin │ │ │ └── compilerplugins │ │ │ └── dataclassgenerate │ │ │ └── examples │ │ │ └── DataClassGenerateSuperClass.kt │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── examples │ │ └── GenerateSuperClassTest.kt └── nosuperclass │ ├── build.gradle.kts │ └── src │ ├── test │ └── kotlin │ │ └── com │ │ └── facebook │ │ └── kotlin │ │ └── compilerplugins │ │ └── dataclassgenerate │ │ └── examples │ │ └── DontGenerateSuperClassTest.kt │ └── main │ └── kotlin │ └── com │ └── facebook │ └── kotlin │ └── compilerplugins │ └── dataclassgenerate │ └── examples │ └── DataClassGenerateSuperClass.kt ├── annotation ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── com │ └── facebook │ └── kotlin │ └── compilerplugins │ └── dataclassgenerate │ └── annotation │ └── DataClassGenerate.kt ├── superclass ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── com │ └── facebook │ └── kotlin │ └── compilerplugins │ └── dataclassgenerate │ └── superclass │ └── DataClassSuper.kt ├── .gitignore ├── .github └── workflows │ ├── pre-merge.yml │ ├── publish-snapshot.yml │ └── publish-release.yml ├── settings.gradle.kts ├── LICENSE ├── CONTRIBUTING.md ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── README.md ├── media ├── logo-white.svg ├── logo-black.svg └── logo-full-color.svg └── gradlew /compiler/cli/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookincubator/dataclassgenerate/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradleplugin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookincubator/dataclassgenerate/HEAD/gradleplugin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /compiler/cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor: -------------------------------------------------------------------------------- 1 | com.facebook.kotlin.compilerplugins.dataclassgenerate.DataClassGenerateCommandLineProcessor 2 | -------------------------------------------------------------------------------- /compiler/cli/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar: -------------------------------------------------------------------------------- 1 | com.facebook.kotlin.compilerplugins.dataclassgenerate.DataClassGenerateComponentRegistrar 2 | -------------------------------------------------------------------------------- /examples/strict/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /gradleplugin/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-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /annotation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | kotlin("multiplatform") 10 | id("publish") 11 | } 12 | 13 | kotlin { 14 | jvm() 15 | jvmToolchain(8) 16 | } 17 | -------------------------------------------------------------------------------- /superclass/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | kotlin("multiplatform") 10 | id("publish") 11 | } 12 | 13 | kotlin { 14 | jvm() 15 | jvmToolchain(8) 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Buck files 4 | /buck-cache/ 5 | /buck-out/ 6 | /.buckconfig.local 7 | /.buckd 8 | /.lsp.buckd 9 | /.lsp-buck-out 10 | /gentest/test.html 11 | .buck-java11 12 | 13 | .gradle 14 | 15 | gradle/wrapper/dists/* 16 | gradle/native/* 17 | gradle/daemon/* 18 | gradle/caches/* 19 | 20 | # Output files 21 | build/ 22 | 23 | # Android Studio/IntelliJ 24 | .idea 25 | .idea/libraries 26 | *.iml 27 | local.properties 28 | -------------------------------------------------------------------------------- /superclass/src/commonMain/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.superclass 9 | 10 | open class DataClassSuper 11 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/misc/IdePsiElementAlias.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | typealias DcgPsiElement = com.intellij.psi.PsiElement 11 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/misc/PsiElementAlias.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | typealias DcgPsiElement = org.jetbrains.kotlin.com.intellij.psi.PsiElement 11 | -------------------------------------------------------------------------------- /examples/innerclass/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | kotlin("jvm") 10 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 11 | } 12 | 13 | dependencies { 14 | testImplementation(libs.junit) 15 | testImplementation(libs.assertj.core) 16 | } 17 | -------------------------------------------------------------------------------- /examples/optout/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | kotlin("jvm") 10 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 11 | } 12 | 13 | dataClassGenerate { enabled.set(false) } 14 | 15 | dependencies { 16 | testImplementation(libs.junit) 17 | testImplementation(libs.assertj.core) 18 | } 19 | -------------------------------------------------------------------------------- /examples/println/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | kotlin("jvm") 10 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 11 | application 12 | } 13 | 14 | application { 15 | mainClass.set( 16 | "com.facebook.kotlin.compilerplugins.dataclassgenerate.sample.DataClassGenerateSampleKt" 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /compiler/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | id("java-library") 10 | kotlin("jvm") 11 | id("publish") 12 | } 13 | 14 | kotlin { jvmToolchain(8) } 15 | 16 | java { 17 | withSourcesJar() 18 | withJavadocJar() 19 | } 20 | 21 | dependencies { 22 | compileOnly(libs.kotlin.compilerEmbeddable) 23 | 24 | testImplementation(libs.kotlin.compilerEmbeddable) 25 | } 26 | -------------------------------------------------------------------------------- /examples/explicit/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGenerateMode 9 | 10 | plugins { 11 | kotlin("jvm") 12 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 13 | } 14 | 15 | dataClassGenerate { mode.set(DataClassGenerateMode.EXPLICIT) } 16 | 17 | dependencies { 18 | testImplementation(libs.junit) 19 | testImplementation(libs.assertj.core) 20 | } 21 | -------------------------------------------------------------------------------- /examples/implicit/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGenerateMode 9 | 10 | plugins { 11 | kotlin("jvm") 12 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 13 | } 14 | 15 | dataClassGenerate { mode.set(DataClassGenerateMode.IMPLICIT) } 16 | 17 | dependencies { 18 | testImplementation(libs.junit) 19 | testImplementation(libs.assertj.core) 20 | } 21 | -------------------------------------------------------------------------------- /compiler/k2/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | id("java-library") 10 | kotlin("jvm") 11 | id("publish") 12 | } 13 | 14 | kotlin { jvmToolchain(8) } 15 | 16 | java { 17 | withSourcesJar() 18 | withJavadocJar() 19 | } 20 | 21 | dependencies { 22 | implementation(project(":annotation")) 23 | implementation(project(":compiler:common")) 24 | compileOnly(libs.kotlin.compilerEmbeddable) 25 | 26 | testImplementation(libs.kotlin.compilerEmbeddable) 27 | } 28 | -------------------------------------------------------------------------------- /gradleplugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | pluginManagement { 9 | repositories { 10 | google() 11 | mavenCentral() 12 | gradlePluginPortal() 13 | } 14 | } 15 | 16 | dependencyResolutionManagement { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | 22 | versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } } 23 | } 24 | 25 | rootProject.name = "gradleplugin" 26 | 27 | includeBuild("../build-logic") 28 | -------------------------------------------------------------------------------- /examples/strict/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/ParcelizeDataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import android.os.Parcelable 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 12 | import kotlinx.parcelize.Parcelize 13 | 14 | @Parcelize @DataClassGenerate data class ParcelizeDataClass(val i: Int, val s: String) : Parcelable 15 | -------------------------------------------------------------------------------- /examples/superclass/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGenerateMode 9 | 10 | plugins { 11 | kotlin("jvm") 12 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 13 | } 14 | 15 | dataClassGenerate { 16 | mode.set(DataClassGenerateMode.IMPLICIT) 17 | generateSuperClass.set(true) 18 | } 19 | 20 | dependencies { 21 | testImplementation(libs.junit) 22 | testImplementation(libs.assertj.core) 23 | } 24 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/ModeExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode 11 | 12 | fun Mode?.asBoolean(defaultIfParameterIsNotPassed: Boolean): Boolean = 13 | when (this) { 14 | Mode.KEEP -> true 15 | Mode.OMIT -> false 16 | // Remove after Kotlin 1.6 17 | else -> defaultIfParameterIsNotPassed 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/pre-merge.yml: -------------------------------------------------------------------------------- 1 | name: Pre Merge Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | gradle: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v3 17 | - name: Configure JDK 18 | uses: actions/setup-java@v3 19 | with: 20 | distribution: 'zulu' 21 | java-version: '11' 22 | - name: Configure Gradle 23 | uses: gradle/actions/setup-gradle@v3 24 | - name: Build the Gradle Plugin 25 | run: ./gradlew -p gradleplugin build 26 | - name: Build the DCG 27 | run: ./gradlew build 28 | -------------------------------------------------------------------------------- /examples/strict/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SerializableDataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import kotlinx.serialization.SerialName 12 | import kotlinx.serialization.Serializable 13 | 14 | @DataClassGenerate 15 | @Serializable 16 | data class SerializableDataClass(@SerialName("name") val str: String) 17 | -------------------------------------------------------------------------------- /examples/nosuperclass/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGenerateMode 9 | 10 | plugins { 11 | kotlin("jvm") 12 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 13 | } 14 | 15 | dataClassGenerate { 16 | mode.set(DataClassGenerateMode.IMPLICIT) 17 | generateSuperClass.set(false) 18 | } 19 | 20 | dependencies { 21 | testImplementation(project(":superclass")) 22 | testImplementation(libs.junit) 23 | testImplementation(libs.assertj.core) 24 | } 25 | -------------------------------------------------------------------------------- /examples/innerclass/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/WrappingInterface.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode 12 | 13 | interface WrappingInterface { 14 | companion object { 15 | val dummy = Any() 16 | } 17 | 18 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 19 | data class InnerDataClass(val foo: String) 20 | } 21 | -------------------------------------------------------------------------------- /compiler/k1/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | id("java-library") 10 | kotlin("jvm") 11 | id("publish") 12 | } 13 | 14 | kotlin { jvmToolchain(8) } 15 | 16 | java { 17 | withSourcesJar() 18 | withJavadocJar() 19 | } 20 | 21 | dependencies { 22 | implementation(project(":annotation")) 23 | implementation(project(":compiler:common")) 24 | compileOnly(libs.kotlin.compilerEmbeddable) 25 | 26 | testImplementation(libs.kotlin.compilerEmbeddable) 27 | } 28 | 29 | sourceSets { 30 | main { 31 | kotlin { 32 | exclude( 33 | "com/facebook/kotlin/compilerplugins/dataclassgenerate/misc/IdePsiElementAlias.kt", 34 | ) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/println/src/main/java/com/facebook/kotlin/compilerplugins/dataclassgenerate/sample/DataClassGenerateSample.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.sample 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | 12 | data class GenerateSample(val foo: Int) 13 | 14 | @DataClassGenerate data class DataClassGenerateSample(val foo: Int) 15 | 16 | fun main() { 17 | // prints "GenerateSample(foo=10)" 18 | println(GenerateSample(10).toString()) 19 | // prints "com.facebook.kotlin.compilerplugins.dataclassgenerate.sample.DataClassGenerateSample@a" 20 | println(DataClassGenerateSample(10).toString()) 21 | } 22 | -------------------------------------------------------------------------------- /compiler/common/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/configuration/DataClassGenerateExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration 9 | 10 | import org.jetbrains.kotlin.name.FqName 11 | import org.jetbrains.kotlin.name.Name 12 | 13 | object DataClassGenerateExt { 14 | 15 | val annotationFqName = 16 | FqName("com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate") 17 | val tsAnnotationArg = Name.identifier("toString") 18 | val eqHcAnnotationArg = Name.identifier("equalsHashCode") 19 | 20 | /** This property could be updated by compiler plugin configs */ 21 | var generateSuperClass: Boolean = true 22 | } 23 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/misc/AnnotationDescriptorExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.misc 9 | 10 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 11 | import org.jetbrains.kotlin.name.Name 12 | import org.jetbrains.kotlin.resolve.constants.EnumValue 13 | 14 | inline fun > AnnotationDescriptor.getValueArgument(name: Name): T? { 15 | val arg = allValueArguments[name] 16 | // It is only possible to retrieve Enum element 17 | require(arg is EnumValue?) 18 | return (arg as? EnumValue)?.let { 19 | java.lang.Enum.valueOf(T::class.java, it.enumEntryName.identifier) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/nosuperclass/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/DontGenerateSuperClassTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.superclass.DataClassSuper 11 | import org.assertj.core.api.Assertions 12 | import org.junit.Test 13 | 14 | class DontGenerateSuperClassTest { 15 | 16 | @Test 17 | fun `super class is generated`() { 18 | Assertions.assertThat(A(42, "")).isNotInstanceOf(DataClassSuper::class.java) 19 | Assertions.assertThat(B(42, "")).isNotInstanceOf(DataClassSuper::class.java) 20 | Assertions.assertThat(C(42, "")).isNotInstanceOf(DataClassSuper::class.java) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/nosuperclass/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/DataClassGenerateSuperClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode 12 | 13 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 14 | data class A(val i: Int, val s: String) 15 | 16 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 17 | data class B(val i: Int, val s: String) 18 | 19 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 20 | data class C(val i: Int, val s: String) 21 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | pluginManagement { 9 | repositories { 10 | google() 11 | mavenCentral() 12 | gradlePluginPortal() 13 | } 14 | } 15 | 16 | dependencyResolutionManagement { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | rootProject.name = "dataclassgenerate" 24 | 25 | include( 26 | ":annotation", 27 | ":compiler:cli", 28 | ":compiler:common", 29 | ":compiler:k1", 30 | ":compiler:k2", 31 | ":superclass", 32 | ":examples:explicit", 33 | ":examples:implicit", 34 | ":examples:innerclass", 35 | ":examples:nosuperclass", 36 | ":examples:optout", 37 | ":examples:println", 38 | ":examples:strict", 39 | ":examples:superclass", 40 | ) 41 | 42 | includeBuild("gradleplugin") 43 | 44 | includeBuild("build-logic") 45 | -------------------------------------------------------------------------------- /examples/innerclass/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SuperWrapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode 12 | 13 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 14 | data class SuperWrapper(val dummy: Any) { 15 | sealed class WrappingSealedClass { 16 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 17 | data class Single(val url: String) : WrappingSealedClass() 18 | 19 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 20 | data class Double(val first: String, val second: String?) : WrappingSealedClass() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /compiler/common/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/configuration/CompilerConfigurationProperty.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration 9 | 10 | import org.jetbrains.kotlin.compiler.plugin.CliOption 11 | import org.jetbrains.kotlin.config.CompilerConfiguration 12 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 13 | 14 | class CompilerConfigurationProperty( 15 | val cliOption: CliOption, 16 | val configurationKey: CompilerConfigurationKey, 17 | val default: T?, 18 | ) { 19 | init { 20 | if (default == null) require(cliOption.required) 21 | } 22 | } 23 | 24 | operator fun CompilerConfiguration.get(property: CompilerConfigurationProperty): T = 25 | if (property.default != null) get(property.configurationKey, property.default) 26 | else get(property.configurationKey)!! 27 | -------------------------------------------------------------------------------- /examples/innerclass/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/InnerDataClassTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import org.assertj.core.api.Assertions.assertThat 11 | import org.junit.Test 12 | 13 | class InnerDataClassTest { 14 | 15 | @Test 16 | fun `inner data class inside interface`() { 17 | val sampleDataClass1 = WrappingInterface.InnerDataClass("Sample") 18 | val sampleDataClass2 = WrappingInterface.InnerDataClass("Sample") 19 | 20 | assertThat(sampleDataClass1).isEqualTo(sampleDataClass2) 21 | assertThat(sampleDataClass1.hashCode()).isEqualTo(sampleDataClass2.hashCode()) 22 | } 23 | 24 | @Test 25 | fun `inner sealed data class`() { 26 | val single: SuperWrapper.WrappingSealedClass = SuperWrapper.WrappingSealedClass.Single("sample") 27 | single.toString() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) Meta Platforms, Inc. and affiliates. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/strict/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGenerateMode 9 | 10 | plugins { 11 | alias(libs.plugins.android.library) 12 | alias(libs.plugins.kotlin.android) 13 | alias(libs.plugins.kotlin.serialization) 14 | id("kotlin-parcelize") 15 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") 16 | } 17 | 18 | android { 19 | compileSdk = 33 20 | namespace = "com.facebook.kotlin.compilerplugins.dataclassgenerate.examples" 21 | } 22 | 23 | dataClassGenerate { 24 | mode.set(DataClassGenerateMode.STRICT) 25 | generateSuperClass.set(true) 26 | } 27 | 28 | dependencies { 29 | implementation(libs.kotlin.parcelizeRuntime) 30 | implementation(libs.kotlin.serializationCoreJvm) 31 | implementation(libs.kotlin.serializationJsonJvm) 32 | 33 | testImplementation(project(":compiler:k1")) 34 | testImplementation(libs.junit) 35 | testImplementation(libs.assertj.core) 36 | } 37 | -------------------------------------------------------------------------------- /examples/superclass/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/DataClassGenerateSuperClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode 12 | import java.util.concurrent.Callable 13 | 14 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 15 | data class A(val i: Int, val s: String) 16 | 17 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 18 | data class B(val i: Int, val s: String) 19 | 20 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 21 | data class C(val i: Int, val s: String) 22 | 23 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 24 | data class DGeneric, U>(val tParam: T, val uParam: U) : Callable { 25 | override fun call(): Any? = null 26 | } 27 | -------------------------------------------------------------------------------- /compiler/cli/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | id("java-library") 10 | kotlin("jvm") 11 | id("publish") 12 | } 13 | 14 | kotlin { jvmToolchain(8) } 15 | 16 | java { 17 | withSourcesJar() 18 | withJavadocJar() 19 | } 20 | 21 | sourceSets { main { resources { setSrcDirs(listOf("resources")) } } } 22 | 23 | dependencies { 24 | implementation(project(":compiler:common")) 25 | implementation(project(":compiler:k1")) 26 | implementation(project(":compiler:k2")) 27 | compileOnly(libs.kotlin.compilerEmbeddable) 28 | 29 | testImplementation(libs.kotlin.compilerEmbeddable) 30 | testImplementation(libs.kotlin.stdlib) 31 | testImplementation(libs.kotlin.reflect) 32 | testImplementation(libs.kotlin.compileTesting) 33 | testImplementation(libs.assertj.core) 34 | testImplementation(libs.junit) 35 | testImplementation(libs.junit) 36 | testImplementation(libs.byte.buddyAgent) 37 | testImplementation(libs.junit) 38 | testImplementation(libs.ow2.asm) 39 | testImplementation(libs.ow2.asmUtil) 40 | testImplementation(libs.mockito.kotlin) 41 | } 42 | -------------------------------------------------------------------------------- /examples/optout/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/DataClassGenerateOptOutTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import org.assertj.core.api.Assertions.assertThat 11 | import org.junit.Test 12 | 13 | class DataClassGenerateOptOutTest { 14 | 15 | private val nativeToStringRegex = 16 | "com.facebook.kotlin.compilerplugins.dataclassgenerate.examples.[A-Za-z]+@[0-9a-f]+" 17 | 18 | @Test 19 | fun `disabled plugin does not remove toString`() { 20 | val sampleDataClass = SampleDataClass("sample with toString") 21 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 22 | } 23 | 24 | @Test 25 | fun `disabled plugin does not remove equals and hashCode`() { 26 | val sampleDataClass1 = SampleDataClass("Sample with equals and hashCode") 27 | val sampleDataClass2 = SampleDataClass("Sample with equals and hashCode") 28 | 29 | assertThat(sampleDataClass1).isEqualTo(sampleDataClass2) 30 | assertThat(sampleDataClass1.hashCode()).isEqualTo(sampleDataClass2.hashCode()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/strict/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SampleDataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.KEEP 12 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.OMIT 13 | 14 | @DataClassGenerate 15 | data class SampleDataClassWithExplicitOverrides(val str: String) { 16 | override fun toString() = "dummy" 17 | 18 | override fun equals(other: Any?) = true 19 | 20 | override fun hashCode() = 42 21 | } 22 | 23 | @DataClassGenerate data class SampleDataClass(val str: String) 24 | 25 | @DataClassGenerate(OMIT, OMIT) data class SampleDataClassGenerationOff(val str: String) 26 | 27 | @DataClassGenerate(toString = KEEP) data class SampleDataClassToString(val str: String) 28 | 29 | @DataClassGenerate(equalsHashCode = KEEP) data class SampleDataClassEqualsHashcode(val str: String) 30 | 31 | @DataClassGenerate(KEEP, KEEP) 32 | data class SampleDataClassToStringEqualsHashcode(val a: Int, val b: String?) 33 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Publish Snapshot 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | if: ${{ github.repository == 'facebookincubator/dataclassgenerate'}} 11 | runs-on: [ubuntu-latest] 12 | env: 13 | GRADLE_OPTS: -Dorg.gradle.parallel=false 14 | 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v3 18 | - name: Configure JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: 'zulu' 22 | java-version: '11' 23 | - name: Configure Gradle 24 | uses: gradle/actions/setup-gradle@v3 25 | 26 | - name: Publish to Maven Local 27 | run: ./gradlew publishAllToMavenLocal 28 | env: 29 | ORG_GRADLE_PROJECT_USE_SNAPSHOT: true 30 | 31 | - name: Upload Build Artifacts 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: 'snapshot-artifacts' 35 | path: '~/.m2/repository/' 36 | 37 | - name: Publish to the Snapshot Repository 38 | run: ./gradlew publishAllToSonatype 39 | env: 40 | ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }} 41 | ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }} 42 | ORG_GRADLE_PROJECT_USE_SNAPSHOT: true 43 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/misc/ClassLiteralExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.misc 9 | 10 | const val OBJECT_LITERAL = "java/lang/Object" 11 | 12 | typealias ClassLiteral = String 13 | 14 | /** 15 | * We want to avoid extra super class generation for all synthetic classes If synthetic class is a 16 | * result of a compiler plugin's codegen we may wrongly inherit original classe's properties like 17 | * `isData` Patterns we want to avoid: 18 | * - kotlin Companion objects 19 | * - kotlinx-serialization $$serializer helpers 20 | * - fb-@Plugin $delegate 21 | */ 22 | fun ClassLiteral.isLikelySynthetic(): Boolean { 23 | /* 24 | data class Foo { 25 | // Do not want to process this companion object Foo$Companion 26 | companion object { 27 | // But want to process this data class Foo$Companion$Bar 28 | data class Bar 29 | } 30 | } 31 | */ 32 | return endsWith("\$Companion") 33 | /* 34 | // Want to process Foo, but not Foo$$serializer 35 | @Serializable 36 | data class Foo(@SerialName("name") val bar: T) 37 | */ 38 | || endsWith("\$\$serializer") || contains("\$delegate") 39 | } 40 | -------------------------------------------------------------------------------- /examples/explicit/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SampleDataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.KEEP 12 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.OMIT 13 | 14 | data class NonAnnotatedDataClass(val str: String) 15 | 16 | @DataClassGenerate 17 | data class SampleDataClassWithExplicitOverrides(val str: String) { 18 | override fun toString() = "dummy" 19 | 20 | override fun equals(other: Any?) = true 21 | 22 | override fun hashCode() = 42 23 | } 24 | 25 | @DataClassGenerate data class SampleDataClass(val str: String) 26 | 27 | @DataClassGenerate(OMIT, OMIT) data class SampleDataClassGenerationOff(val str: String) 28 | 29 | @DataClassGenerate(toString = KEEP) data class SampleDataClassToString(val str: String) 30 | 31 | @DataClassGenerate(equalsHashCode = KEEP) data class SampleDataClassEqualsHashcode(val str: String) 32 | 33 | @DataClassGenerate(KEEP, KEEP) 34 | data class SampleDataClassToStringEqualsHashcode(val a: Int, val b: String?) 35 | -------------------------------------------------------------------------------- /examples/implicit/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SampleDataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.KEEP 12 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.OMIT 13 | 14 | data class NonAnnotatedDataClass(val str: String) 15 | 16 | @DataClassGenerate 17 | data class SampleDataClassWithExplicitOverrides(val str: String) { 18 | override fun toString() = "dummy" 19 | 20 | override fun equals(other: Any?) = true 21 | 22 | override fun hashCode() = 42 23 | } 24 | 25 | @DataClassGenerate data class SampleDataClass(val str: String) 26 | 27 | @DataClassGenerate(OMIT, OMIT) data class SampleDataClassGenerationOff(val str: String) 28 | 29 | @DataClassGenerate(toString = KEEP) data class SampleDataClassToString(val str: String) 30 | 31 | @DataClassGenerate(equalsHashCode = KEEP) data class SampleDataClassEqualsHashcode(val str: String) 32 | 33 | @DataClassGenerate(KEEP, KEEP) 34 | data class SampleDataClassToStringEqualsHashcode(val a: Int, val b: String?) 35 | -------------------------------------------------------------------------------- /examples/optout/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SampleDataClass.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.KEEP 12 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.OMIT 13 | 14 | data class NonAnnotatedDataClass(val str: String) 15 | 16 | @DataClassGenerate 17 | data class SampleDataClassWithExplicitOverrides(val str: String) { 18 | override fun toString() = "dummy" 19 | 20 | override fun equals(other: Any?) = true 21 | 22 | override fun hashCode() = 42 23 | } 24 | 25 | @DataClassGenerate data class SampleDataClass(val str: String) 26 | 27 | @DataClassGenerate(OMIT, OMIT) data class SampleDataClassGenerationOff(val str: String) 28 | 29 | @DataClassGenerate(toString = KEEP) data class SampleDataClassToString(val str: String) 30 | 31 | @DataClassGenerate(equalsHashCode = KEEP) data class SampleDataClassEqualsHashcode(val str: String) 32 | 33 | @DataClassGenerate(KEEP, KEEP) 34 | data class SampleDataClassToStringEqualsHashcode(val a: Int, val b: String?) 35 | -------------------------------------------------------------------------------- /compiler/common/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/configuration/PluginMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration 9 | 10 | enum class PluginMode { 11 | /** Plugin applies only to data classes annotated with [@DataClassGenerate] */ 12 | EXPLICIT, 13 | 14 | /** 15 | * Same as [EXPLICIT], but Plugin will report a compilation error whenever it see a data class 16 | * without [@DataClassGenerate] annotation. 17 | * 18 | * Example 1: Plugin will generate `toString`, but omit `equals` and 19 | * `hashCode`. @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.OMIT) data class 20 | * A(val i:Int) 21 | * 22 | * Example 2: Plugin will report a compilation error data class B(val l:Long) 23 | */ 24 | STRICT, 25 | 26 | /** 27 | * Plugin applies to all data classes, if data class is not annotated with [@DataClassGenerate] it 28 | * will be treated as annotated with [@DataClassGenerate] 29 | * 30 | * Example: Plugin will generate a `toString` method for data class [A] Plugin will NOT generate 31 | * `equals` and `hashCode`. @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.OMIT) 32 | * data class A(val i: Int) 33 | */ 34 | IMPLICIT, 35 | } 36 | -------------------------------------------------------------------------------- /compiler/cli/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/CompilerConfigurationPropertyTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperty 11 | import org.assertj.core.api.Assertions.assertThatThrownBy 12 | import org.jetbrains.kotlin.compiler.plugin.CliOption 13 | import org.junit.Test 14 | import org.mockito.Mockito.`when` 15 | import org.mockito.kotlin.mock 16 | 17 | class CompilerConfigurationPropertyTest { 18 | 19 | private val requiredCliOption = mock().apply { `when`(this.required).thenReturn(true) } 20 | private val notRequiredCliOption = 21 | mock().apply { `when`(this.required).thenReturn(false) } 22 | 23 | @Test 24 | fun `fail CompilerConfigurationProperty init if default is null and cli option not required`() { 25 | assertThatThrownBy { 26 | CompilerConfigurationProperty(notRequiredCliOption, mock(), null) 27 | } 28 | .isInstanceOf(IllegalArgumentException::class.java) 29 | } 30 | 31 | @Test 32 | fun `CompilerConfigurationProperty init if default is null and cli option required`() { 33 | CompilerConfigurationProperty(requiredCliOption, mock(), null) 34 | } 35 | 36 | @Test 37 | fun `CompilerConfigurationProperty init if default is not null and option required`() { 38 | CompilerConfigurationProperty(requiredCliOption, mock(), true) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/visitor/SuperClassInitCallOverrideVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.visitor 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.GENERATED_SUPER 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.OBJECT_LITERAL 12 | import org.jetbrains.org.objectweb.asm.MethodVisitor 13 | import org.jetbrains.org.objectweb.asm.Opcodes 14 | 15 | class SuperClassInitCallOverrideVisitor(original: MethodVisitor) : 16 | MethodVisitor(Opcodes.ASM5, original) { 17 | override fun visitMethodInsn( 18 | opcode: Int, 19 | owner: String?, 20 | name: String?, 21 | descriptor: String?, 22 | isInterface: Boolean, 23 | ) { 24 | // Having: 25 | // opcode owner name 26 | // \/ \/ \/ 27 | // invokespecial #20 // Method java/lang/Object."":()V 28 | // 29 | // Changing to: 30 | // invokespecial #20 // Method 31 | // com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper."":()V 32 | // 33 | // If we have multiple constructors in Data Class, only one will have an j/l/Object owner. 34 | // 35 | var effectiveOwner = owner 36 | if (owner == OBJECT_LITERAL && opcode == Opcodes.INVOKESPECIAL) { 37 | effectiveOwner = GENERATED_SUPER 38 | } 39 | super.visitMethodInsn(opcode, effectiveOwner, name, descriptor, isInterface) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /compiler/common/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/configuration/CompilerConfigurationProperties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration 9 | 10 | import org.jetbrains.kotlin.compiler.plugin.CliOption 11 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 12 | 13 | object CompilerConfigurationProperties { 14 | val ENABLED = 15 | CompilerConfigurationProperty( 16 | CliOption("enabled", "", "whether plugin is enabled", required = false), 17 | CompilerConfigurationKey("enabled"), 18 | true, 19 | ) 20 | val MODE = 21 | CompilerConfigurationProperty( 22 | CliOption( 23 | "mode", 24 | PluginMode.values().joinToString(prefix = "<", postfix = ">", separator = " | "), 25 | "defines plugin mode, check PluginMode.kt enum for more details}", 26 | required = false, 27 | ), 28 | CompilerConfigurationKey("mode"), 29 | PluginMode.EXPLICIT, 30 | ) 31 | 32 | val GENERATE_SUPER_CLASS = 33 | CompilerConfigurationProperty( 34 | CliOption( 35 | "generateSuperClass", 36 | "", 37 | "whether a super class should be generated for appropriate data classes", 38 | required = false, 39 | ), 40 | CompilerConfigurationKey("generateSuperClass"), 41 | true, 42 | ) 43 | val all 44 | get() = listOf(ENABLED, MODE, GENERATE_SUPER_CLASS) 45 | } 46 | -------------------------------------------------------------------------------- /examples/strict/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/SerializableDataClassTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.GENERATED_SUPER 11 | import org.assertj.core.api.Assertions 12 | import org.junit.Test 13 | 14 | private const val SERIALIZABLE_DATA_CLASS_LITERAL = 15 | "com.facebook.kotlin.compilerplugins.dataclassgenerate.examples.SerializableDataClass" 16 | private val DCG_SUPER_LITERAL = GENERATED_SUPER.replace("/", ".") 17 | 18 | class SerializableDataClassTest { 19 | @Test 20 | fun `DCG supplies DataClassSuper as a super class for non-synthetic`() { 21 | val serializerSuperClass = Class.forName(SERIALIZABLE_DATA_CLASS_LITERAL).superclass 22 | Assertions.assertThat(serializerSuperClass.canonicalName).isEqualTo(DCG_SUPER_LITERAL) 23 | } 24 | 25 | @Test 26 | fun `DCG does not supply DataClassSuper as a super class of synthetic $$serializer`() { 27 | val serializerSuperClass = 28 | Class.forName("$SERIALIZABLE_DATA_CLASS_LITERAL\$\$serializer").superclass 29 | Assertions.assertThat(serializerSuperClass.canonicalName).isNotEqualTo(DCG_SUPER_LITERAL) 30 | } 31 | 32 | @Test 33 | fun `DCG does not supply DataClassSuper as a super class of synthetic $Companion`() { 34 | val serializerSuperClass = 35 | Class.forName("$SERIALIZABLE_DATA_CLASS_LITERAL\$Companion").superclass 36 | Assertions.assertThat(serializerSuperClass.canonicalName).isNotEqualTo(DCG_SUPER_LITERAL) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to DataClassGenerate 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Our Development Process 6 | We develop on a private branch internally at Meta. We regularly update this github project with the changes from the internal repo. External pull requests are cherry-picked into our repo and then pushed back out. 7 | 8 | ## Pull Requests 9 | We actively welcome your pull requests. 10 | 11 | 1. Fork the repo and create your branch from `main`. 12 | 2. If you've added code that should be tested, add tests. 13 | 3. If you've changed APIs, update the documentation. 14 | 4. Ensure the test suite passes. 15 | 5. Make sure your code lints. 16 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 17 | 18 | ## Contributor License Agreement ("CLA") 19 | In order to accept your pull request, we need you to submit a CLA. You only need 20 | to do this once to work on any of Meta's open source projects. 21 | 22 | Complete your CLA here: 23 | 24 | ## Issues 25 | We use GitHub issues to track public bugs. Please ensure your description is 26 | clear and has sufficient instructions to be able to reproduce the issue. 27 | 28 | Meta has a [bounty program](https://www.facebook.com/whitehat/) for the safe 29 | disclosure of security bugs. In those cases, please go through the process 30 | outlined on that page and do not file a public issue. 31 | 32 | ## Coding Style 33 | We follow the [Kotlin style guide](https://developer.android.com/kotlin/style-guide). 34 | 35 | Most importantly, be consistent with existing code. Look around the codebase and match the style. 36 | 37 | ## License 38 | By contributing to DataClassGenerate, you agree that your contributions will be licensed 39 | under the LICENSE file in the root directory of this source tree. 40 | -------------------------------------------------------------------------------- /compiler/cli/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DataClassGenerateComponentRegistrar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // TODO: resolve deprecation errors in DataClassGenerateInterceptorExtension T161233385 9 | @file:Suppress("DEPRECATION_ERROR") 10 | 11 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 12 | 13 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties.ENABLED 14 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties.GENERATE_SUPER_CLASS 15 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties.MODE 16 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.DataClassGenerateExt 17 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.get 18 | import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension 19 | import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar 20 | import org.jetbrains.kotlin.config.CompilerConfiguration 21 | 22 | @OptIn(org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi::class) 23 | class DataClassGenerateComponentRegistrar : CompilerPluginRegistrar() { 24 | 25 | override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { 26 | if (configuration[ENABLED]) { 27 | DataClassGenerateExt.generateSuperClass = configuration[GENERATE_SUPER_CLASS] 28 | 29 | ClassBuilderInterceptorExtension.registerExtension( 30 | DataClassGenerateInterceptorExtension(configuration[MODE]) 31 | ) 32 | } 33 | } 34 | 35 | override val supportsK2 36 | get() = true 37 | } 38 | -------------------------------------------------------------------------------- /compiler/cli/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DataClassGenerateCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties.ENABLED 12 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties.GENERATE_SUPER_CLASS 13 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties.MODE 14 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.PluginMode 15 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 16 | import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException 17 | import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor 18 | import org.jetbrains.kotlin.config.CompilerConfiguration 19 | 20 | @OptIn(org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi::class) 21 | class DataClassGenerateCommandLineProcessor : CommandLineProcessor { 22 | override val pluginId: String = "com.facebook.kotlin.dataclassgenerate" 23 | override val pluginOptions: Collection = 24 | CompilerConfigurationProperties.all.map { it.cliOption } 25 | 26 | override fun processOption( 27 | option: AbstractCliOption, 28 | value: String, 29 | configuration: CompilerConfiguration, 30 | ) { 31 | when (option) { 32 | ENABLED.cliOption -> configuration.put(ENABLED.configurationKey, value.toBoolean()) 33 | MODE.cliOption -> configuration.put(MODE.configurationKey, PluginMode.valueOf(value)) 34 | GENERATE_SUPER_CLASS.cliOption -> 35 | configuration.put(GENERATE_SUPER_CLASS.configurationKey, value.toBoolean()) 36 | else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DataClassGenerateInterceptorExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // TODO: resolve deprecation errors in DataClassGenerateInterceptorExtension T161233385 9 | @file:Suppress("DEPRECATION_ERROR") 10 | 11 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 12 | 13 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.PluginMode 14 | import org.jetbrains.kotlin.codegen.ClassBuilder 15 | import org.jetbrains.kotlin.codegen.ClassBuilderFactory 16 | import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension 17 | import org.jetbrains.kotlin.diagnostics.DiagnosticSink 18 | import org.jetbrains.kotlin.resolve.BindingContext 19 | import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin 20 | 21 | class DataClassGenerateInterceptorExtension( 22 | val pluginMode: PluginMode, 23 | ) : ClassBuilderInterceptorExtension { 24 | override fun interceptClassBuilderFactory( 25 | interceptedFactory: ClassBuilderFactory, 26 | bindingContext: BindingContext, 27 | diagnostics: DiagnosticSink, 28 | ): ClassBuilderFactory { 29 | return object : ClassBuilderFactory by interceptedFactory { 30 | override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder = 31 | DataClassGenerateBuilder(origin, interceptedFactory.newClassBuilder(origin), pluginMode) 32 | 33 | // Overriding [asBytes] and [asText] methods to be compatible with other 34 | // android compiler plugins 35 | // Delegation here leads to ClassCastException if we work with Parcelize 36 | // or Android Extensions 37 | override fun asBytes(builder: ClassBuilder?): ByteArray { 38 | return interceptedFactory.asBytes((builder as DataClassGenerateBuilder).classBuilder) 39 | } 40 | 41 | override fun asText(builder: ClassBuilder?): String { 42 | return interceptedFactory.asText((builder as DataClassGenerateBuilder).classBuilder) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /compiler/cli/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DumpingVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import java.io.File 11 | import org.objectweb.asm.ClassReader 12 | import org.objectweb.asm.ClassVisitor 13 | import org.objectweb.asm.MethodVisitor 14 | import org.objectweb.asm.Opcodes.ASM5 15 | import org.objectweb.asm.util.Textifier 16 | import org.objectweb.asm.util.TraceMethodVisitor 17 | 18 | class DcgDumpClassVisitor(val dcgDump: DcgDump) : ClassVisitor(ASM5) { 19 | override fun visitMethod( 20 | access: Int, 21 | name: String, 22 | desc: String, 23 | signature: String?, 24 | exceptions: Array?, 25 | ): MethodVisitor { 26 | val original = super.visitMethod(access, name, desc, signature, exceptions) 27 | val methodContextDumper = 28 | object : Textifier(ASM5) { 29 | override fun visitMethodEnd() { 30 | when (name) { 31 | "toString" -> dcgDump.toStringDeclaration = text.toString() 32 | "hashCode" -> dcgDump.hashCodeDeclaration = text.toString() 33 | "equals" -> dcgDump.equalsDeclaration = text.toString() 34 | } 35 | } 36 | } 37 | return TraceMethodVisitor(original, methodContextDumper) 38 | } 39 | 40 | override fun visit( 41 | version: Int, 42 | access: Int, 43 | name: String, 44 | signature: String?, 45 | superName: String, 46 | interfaces: Array, 47 | ) { 48 | dcgDump.superClass = superName 49 | } 50 | } 51 | 52 | data class DcgDump( 53 | var superClass: String? = null, 54 | var toStringDeclaration: String? = null, 55 | var hashCodeDeclaration: String? = null, 56 | var equalsDeclaration: String? = null, 57 | ) 58 | 59 | fun dumpClassFileAbi(classFile: File): DcgDump { 60 | val cr = ClassReader(classFile.readBytes()) 61 | val dump = DcgDump() 62 | cr.accept(DcgDumpClassVisitor(dump), 0) 63 | return dump 64 | } 65 | 66 | fun Collection.asDcgMap(): Map = 67 | filter { it.name.endsWith(".class") }.associate { it.name to dumpClassFileAbi(it) } 68 | -------------------------------------------------------------------------------- /gradleplugin/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/gradle/DataClassGeneratePluginExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle 9 | 10 | import javax.inject.Inject 11 | import org.gradle.api.model.ObjectFactory 12 | import org.gradle.api.provider.Property 13 | 14 | public abstract class DataClassGeneratePluginExtension @Inject constructor(objects: ObjectFactory) { 15 | public val enabled: Property = 16 | objects.property(Boolean::class.javaObjectType).convention(true) 17 | 18 | public val mode: Property = 19 | objects.property(DataClassGenerateMode::class.java).convention(DataClassGenerateMode.EXPLICIT) 20 | 21 | /** 22 | * Adds a marker super class [DataClassSuper] for suitable data classes to make them available for 23 | * [Redex Class Merging Optimization](https://github.com/facebook/redex/blob/main/docs/passes.md#classmergingpass). 24 | */ 25 | public val generateSuperClass: Property = 26 | objects.property(Boolean::class.javaObjectType).convention(false) 27 | } 28 | 29 | enum class DataClassGenerateMode { 30 | /** Plugin applies only to data classes annotated with [@DataClassGenerate] */ 31 | EXPLICIT, 32 | 33 | /** 34 | * Same as [EXPLICIT], but Plugin will report a compilation error whenever it see a data class 35 | * without [@DataClassGenerate] annotation. 36 | * 37 | * Example 1: Plugin will generate `toString`, but omit `equals` and 38 | * `hashCode`. @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.OMIT) data class 39 | * A(val i:Int) 40 | * 41 | * Example 2: Plugin will report a compilation error data class B(val l:Long) 42 | */ 43 | STRICT, 44 | 45 | /** 46 | * Plugin applies to all data classes, if data class is not annotated with [@DataClassGenerate] it 47 | * will be treated as annotated with [@DataClassGenerate] 48 | * 49 | * Example: Plugin will generate a `toString` method for data class [A] Plugin will NOT generate 50 | * `equals` and `hashCode`. @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.OMIT) 51 | * data class A(val i: Int) 52 | */ 53 | IMPLICIT, 54 | } 55 | -------------------------------------------------------------------------------- /examples/superclass/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/GenerateSuperClassTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.superclass.DataClassSuper 11 | import org.assertj.core.api.Assertions 12 | import org.junit.Test 13 | import org.objectweb.asm.ClassReader 14 | import org.objectweb.asm.ClassVisitor 15 | import org.objectweb.asm.Opcodes 16 | import org.objectweb.asm.signature.SignatureReader 17 | import org.objectweb.asm.util.TraceSignatureVisitor 18 | 19 | class GenerateSuperClassTest { 20 | 21 | @Test 22 | fun `super class is generated`() { 23 | Assertions.assertThat(A(42, "")).isInstanceOf(DataClassSuper::class.java) 24 | Assertions.assertThat(B(42, "")).isInstanceOf(DataClassSuper::class.java) 25 | Assertions.assertThat(C(42, "")).isInstanceOf(DataClassSuper::class.java) 26 | } 27 | 28 | @Test 29 | fun `super class for generic type`() { 30 | Assertions.assertThat(DGeneric, String>(emptyList(), "")) 31 | .isInstanceOf(DataClassSuper::class.java) 32 | } 33 | 34 | @Test 35 | fun `signature for generic type`() { 36 | val classReader = ClassReader(DGeneric::class.java.name) 37 | val traceSignatureVisitor = TraceSignatureVisitor(0) 38 | 39 | classReader.accept( 40 | object : ClassVisitor(Opcodes.ASM9) { 41 | override fun visit( 42 | version: Int, 43 | access: Int, 44 | name: String?, 45 | signature: String?, 46 | superName: String?, 47 | interfaces: Array?, 48 | ) { 49 | SignatureReader(signature).accept(traceSignatureVisitor) 50 | super.visit(version, access, name, signature, superName, interfaces) 51 | } 52 | }, 53 | 0, 54 | ) 55 | Assertions.assertThat(traceSignatureVisitor.declaration) 56 | .isEqualTo( 57 | ", U> extends com.facebook.kotlin.compilerplugins.dataclassgenerate.superclass.DataClassSuper implements java.util.concurrent.Callable" 58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gradleplugin/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/gradle/DataClassGeneratePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle 9 | 10 | import org.gradle.api.Project 11 | import org.gradle.api.provider.Provider 12 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 13 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin 14 | import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact 15 | import org.jetbrains.kotlin.gradle.plugin.SubpluginOption 16 | 17 | class DataClassGeneratePlugin : KotlinCompilerPluginSupportPlugin { 18 | override fun getCompilerPluginId() = "com.facebook.kotlin.dataclassgenerate" 19 | 20 | override fun apply(target: Project) { 21 | target.extensions.create("dataClassGenerate", DataClassGeneratePluginExtension::class.java) 22 | } 23 | 24 | override fun getPluginArtifact() = 25 | SubpluginArtifact( 26 | "com.facebook.kotlin.compilerplugins.dataclassgenerate", 27 | "cli", 28 | "$dcgVersion", 29 | ) 30 | 31 | override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { 32 | return kotlinCompilation.target.project.plugins.hasPlugin(DataClassGeneratePlugin::class.java) 33 | } 34 | 35 | override fun applyToCompilation( 36 | kotlinCompilation: KotlinCompilation<*> 37 | ): Provider> { 38 | val project = kotlinCompilation.target.project 39 | val extension = project.extensions.getByType(DataClassGeneratePluginExtension::class.java) 40 | 41 | kotlinCompilation.dependencies { 42 | compileOnly("com.facebook.kotlin.compilerplugins.dataclassgenerate:annotation:$dcgVersion") 43 | } 44 | 45 | if (extension.generateSuperClass.get()) { 46 | kotlinCompilation.dependencies { 47 | implementation( 48 | "com.facebook.kotlin.compilerplugins.dataclassgenerate:superclass:$dcgVersion" 49 | ) 50 | } 51 | } 52 | return project.provider { 53 | listOf( 54 | SubpluginOption(key = "enabled", value = extension.enabled.get().toString()), 55 | SubpluginOption(key = "mode", value = extension.mode.get().toString()), 56 | SubpluginOption( 57 | key = "generateSuperClass", 58 | value = extension.generateSuperClass.get().toString(), 59 | ), 60 | ) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/visitor/Visitors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.visitor 9 | 10 | import org.jetbrains.org.objectweb.asm.MethodVisitor 11 | import org.jetbrains.org.objectweb.asm.Opcodes.ALOAD 12 | import org.jetbrains.org.objectweb.asm.Opcodes.ARETURN 13 | import org.jetbrains.org.objectweb.asm.Opcodes.ASM5 14 | import org.jetbrains.org.objectweb.asm.Opcodes.INVOKESPECIAL 15 | import org.jetbrains.org.objectweb.asm.Opcodes.IRETURN 16 | 17 | class HashCodeMethodVisitor(private val owner: String, original: MethodVisitor) : 18 | MethodVisitor(ASM5, original) { 19 | /* 20 | descriptor: ()I 21 | Code: 22 | 0: aload_0 23 | Method com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper.hashCode:()I 24 | 1: invokespecial 25 | 4: ireturn 26 | */ 27 | override fun visitCode() { 28 | mv.apply { 29 | visitVarInsn(ALOAD, 0) 30 | visitMethodInsn(INVOKESPECIAL, owner, "hashCode", "()I", false) 31 | visitInsn(IRETURN) 32 | } 33 | } 34 | } 35 | 36 | class EqualsMethodVisitor(private val owner: String, original: MethodVisitor) : 37 | MethodVisitor(ASM5, original) { 38 | /* 39 | descriptor: (Ljava/lang/Object;)Z 40 | Code: 41 | 0: aload_0 42 | 1: aload_1 43 | Method com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper.equals:(Ljava/lang/Object;)Z 44 | 2: invokespecial 45 | 5: ireturn 46 | */ 47 | override fun visitCode() { 48 | mv.apply { 49 | visitVarInsn(ALOAD, 0) 50 | visitVarInsn(ALOAD, 1) 51 | visitMethodInsn(INVOKESPECIAL, owner, "equals", "(Ljava/lang/Object;)Z", false) 52 | visitInsn(IRETURN) 53 | } 54 | } 55 | } 56 | 57 | class ToStringMethodVisitor(private val owner: String, original: MethodVisitor) : 58 | MethodVisitor(ASM5, original) { 59 | /* 60 | descriptor: ()Ljava/lang/String; 61 | Code: 62 | 0: aload_0 63 | Method com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper.toString:()Ljava/lang/String; 64 | 1: invokespecial 65 | 4: areturn 66 | */ 67 | override fun visitCode() { 68 | mv.apply { 69 | visitVarInsn(ALOAD, 0) 70 | visitMethodInsn(INVOKESPECIAL, owner, "toString", "()Ljava/lang/String;", false) 71 | visitInsn(ARETURN) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) Meta Platforms, Inc. and affiliates. 3 | # 4 | # This source code is licensed under the MIT license found in the 5 | # LICENSE file in the root directory of this source tree. 6 | # 7 | 8 | [versions] 9 | kotlin = "2.1.0" 10 | agp = "7.4.1" 11 | byte-buddy-agent = "1.12.18" 12 | ow2-asm = "9.4" 13 | kotlin-compile-testing = "0.8.0" 14 | kotlin-serialization-jvm = "1.3.0" 15 | mockito-kotlin = "2.2.11" 16 | assertj-core = "2.9.0" 17 | junit = "4.13.2" 18 | robolectric = "4.9" 19 | nexus-publish-plugin = "2.0.0" 20 | gradle-publish-plugin = "1.2.0" 21 | 22 | [plugins] 23 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} 24 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin"} 25 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"} 26 | android-library= { id = "com.android.library", version.ref = "agp"} 27 | nexus-publish-plugin = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish-plugin"} 28 | gradle-publish-plugin = { id = "com.gradle.plugin-publish", version.ref = "gradle-publish-plugin"} 29 | 30 | [libraries] 31 | kotlin-compilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } 32 | kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 33 | kotlin-gradlePlugin-api = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin" } 34 | kotlin-stdlib = {module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin"} 35 | kotlin-reflect = {module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin"} 36 | kotlin-parcelizeRuntime = {module = "org.jetbrains.kotlin:kotlin-parcelize-runtime", version.ref = "kotlin"} 37 | kotlin-serializationCoreJvm = {module = "org.jetbrains.kotlinx:kotlinx-serialization-core-jvm", version.ref = "kotlin-serialization-jvm"} 38 | kotlin-serializationJsonJvm = {module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version.ref = "kotlin-serialization-jvm"} 39 | kotlin-compileTesting = {module = "dev.zacsweers.kctfork:core", version.ref = "kotlin-compile-testing"} 40 | byte-buddyAgent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byte-buddy-agent"} 41 | ow2-asm = {module = "org.ow2.asm:asm", version.ref = "ow2-asm"} 42 | ow2-asmUtil = {module = "org.ow2.asm:asm-util", version.ref = "ow2-asm"} 43 | mockito-kotlin = {module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-kotlin"} 44 | assertj-core = {module = "org.assertj:assertj-core", version.ref = "assertj-core"} 45 | junit = {module = "junit:junit", version.ref = "junit"} 46 | robolectric = {module = "org.robolectric:robolectric", version.ref = "robolectric"} 47 | -------------------------------------------------------------------------------- /annotation/src/commonMain/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/annotation/DataClassGenerate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation 9 | 10 | enum class Mode { 11 | /** Express an intent to keep/generate a method(s) DataClassGenerate. */ 12 | KEEP, 13 | /** Express an intent to omit method(s) generation. */ 14 | OMIT, 15 | } 16 | 17 | @Target(AnnotationTarget.CLASS) 18 | /** 19 | * Configures Kotlin Data Classes code generation and bytecode optimizations. [@DataClassGenerate] 20 | * is applicable only to Kotlin Data Classes. 21 | * 22 | * DataClassGenerate Kotlin Compiler Plugin processes @DataClassGenerate annotation. 23 | * 24 | * [@DataClassGenerate] annotation together with applied DataClassGenerate compiler plugin does 3 25 | * things: 26 | * 1. Configures code generation of `toString` method. 27 | * - `@DataClassGenerate(toString = Mode.KEEP)` will keep(generate) `toString` as in usual Data 28 | * Class. 29 | * - `@DataClassGenerate(toString = Mode.OMIT)` will omit(NOT generate) `toString` for an annotated 30 | * Data Class. 31 | * 2. Configures code generation of `equals` and `hashCode` methods. 32 | * - `@DataClassGenerate(equalsHashCode = Mode.KEEP)` will keep(generate) `equals` and `hashCode` as 33 | * in usual Data Class. 34 | * - `@DataClassGenerate(equalsHashCode = Mode.OMIT)` will omit (NOT generate) `equals` and 35 | * `hashCode` for an annotated Data Class. 36 | * 3. Adds a marker super class `DataClassSuper` for suitable Data Classes, to make them available 37 | * for Redex Class Merging Optimization. 38 | * 39 | * DataClassGenerate compiler plugin has multiple modes: 40 | * - EXPLICIT - The default mode. If Data Class is annotated with @DataClassGenerate - processes it 41 | * as declared in annotation. If Data Class is NOT annotated with @DataClassGenerate - does 42 | * nothing. 43 | * - STRICT - Forces module developers to use Data Classes only with @DataClassGenerate. If Data 44 | * Class is annotated with @DataClassGenerate - processes it as declared in annotation. If Data 45 | * Class is NOT annotated with @DataClassGenerate - reports with a compilation error. 46 | * - IMPLICIT - The default mode. If Data Class is annotated with @DataClassGenerate - processes it 47 | * as declared in annotation. If Data Class is NOT annotated with @DataClassGenerate - processes 48 | * it as it is annotated with `@DataClassGenerate(toString=Mode.OMIT, equalsHashCode=Mode.KEEP)` 49 | */ 50 | annotation class DataClassGenerate( 51 | @get:JvmName("toString_uniqueJvmName") val toString: Mode = Mode.OMIT, 52 | val equalsHashCode: Mode = Mode.KEEP, 53 | ) 54 | -------------------------------------------------------------------------------- /gradleplugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | plugins { 9 | alias(libs.plugins.kotlin) 10 | alias(libs.plugins.nexus.publish.plugin) 11 | alias(libs.plugins.gradle.publish.plugin) 12 | id("java-gradle-plugin") 13 | id("publish") 14 | } 15 | 16 | group = "com.facebook.kotlin.compilerplugins.dataclassgenerate" 17 | 18 | val nexusUsername = findProperty("NEXUS_USERNAME")?.toString() 19 | val nexusPassword = findProperty("NEXUS_PASSWORD")?.toString() 20 | 21 | nexusPublishing { 22 | repositories { 23 | sonatype { 24 | username.set(nexusUsername) 25 | password.set(nexusPassword) 26 | nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) 27 | snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | compileOnly(libs.kotlin.gradlePlugin) 34 | compileOnly(libs.kotlin.gradlePlugin.api) 35 | } 36 | 37 | kotlin { jvmToolchain(8) } 38 | 39 | java { 40 | withSourcesJar() 41 | withJavadocJar() 42 | } 43 | 44 | gradlePlugin { 45 | website.set("https://github.com/facebookincubator/dataclassgenerate") 46 | vcsUrl.set("https://github.com/facebookincubator/dataclassgenerate") 47 | plugins { 48 | create("dataClassGeneratePlugin") { 49 | id = "com.facebook.kotlin.compilerplugins.dataclassgenerate" 50 | implementationClass = 51 | "com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGeneratePlugin" 52 | displayName = "Data Class Generate" 53 | description = 54 | "Data Class Generate is a Kotlin compiler plugin that helps to address Kotlin data class binary size overhead" 55 | tags.set(listOf("kotlin", "compiler", "dataclass", "optimization")) 56 | } 57 | } 58 | } 59 | 60 | val versionDirectory = "$buildDir/generated/version/" 61 | 62 | sourceSets { main { java.srcDir(versionDirectory) } } 63 | 64 | val pluginVersionTask = 65 | tasks.register("pluginVersion") { 66 | val outputDir = file(versionDirectory) 67 | inputs.property("version", version) 68 | outputs.dir(outputDir) 69 | doLast { 70 | val versionFile = 71 | file( 72 | "$outputDir/com/facebook/kotlin/compilerplugins/dataclassgenerata/gradle/version.kt" 73 | ) 74 | versionFile.parentFile.mkdirs() 75 | versionFile.writeText( 76 | """// Generated file. Do not edit! 77 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle 78 | internal const val dcgVersion = "$version" 79 | """, 80 | Charsets.UTF_8, 81 | ) 82 | } 83 | } 84 | 85 | tasks.getByName("compileKotlin").dependsOn(pluginVersionTask) 86 | 87 | tasks.getByName("sourcesJar").dependsOn(pluginVersionTask) 88 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | workflow_dispatch: 7 | inputs: 8 | ref: 9 | description: "Git Tag or Ref to publish from. Default to main, but you can specify a tag to retrigger publishing" 10 | required: true 11 | default: 'main' 12 | 13 | jobs: 14 | publish: 15 | if: ${{ github.repository == 'facebookincubator/dataclassgenerate'}} 16 | runs-on: [ubuntu-latest] 17 | env: 18 | GRADLE_OPTS: -Dorg.gradle.parallel=false 19 | 20 | steps: 21 | - name: Checkout Repo 22 | uses: actions/checkout@v3 23 | with: 24 | ref: ${{ inputs.ref }} 25 | - name: Configure JDK 26 | uses: actions/setup-java@v3 27 | with: 28 | distribution: 'zulu' 29 | java-version: '11' 30 | - name: Configure Gradle 31 | uses: gradle/actions/setup-gradle@v3 32 | 33 | - name: Publish to Maven Local 34 | run: ./gradlew publishAllToMavenLocal 35 | env: 36 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} 37 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} 38 | 39 | - name: Upload Build Artifacts 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: 'snapshot-artifacts' 43 | path: '~/.m2/repository/' 44 | 45 | - name: Publish the Artifact to Maven Central 46 | run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository 47 | env: 48 | ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }} 49 | ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }} 50 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} 51 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} 52 | 53 | - name: Publish the Gradle Plugin to Maven Central 54 | run: ./gradlew -p gradleplugin publishToSonatype closeAndReleaseSonatypeStagingRepository 55 | env: 56 | ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }} 57 | ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }} 58 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} 59 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} 60 | 61 | - name: Publish the Gradle Plugin to Gradle Portal 62 | run: ./gradlew -p gradleplugin publishPlugins 63 | env: 64 | GRADLE_PUBLISH_KEY: ${{ secrets.ORG_GRADLE_PROJECT_GRADLE_PUBLISH_KEY }} 65 | GRADLE_PUBLISH_SECRET: ${{ secrets.ORG_GRADLE_PROJECT_GRADLE_PUBLISH_SECRET }} 66 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} 67 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} 68 | -------------------------------------------------------------------------------- /examples/explicit/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/DataClassGenerateTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import org.assertj.core.api.Assertions.assertThat 11 | import org.junit.Test 12 | 13 | class DataClassGenerateTest { 14 | 15 | private val nativeToStringRegex = 16 | "com.facebook.kotlin.compilerplugins.dataclassgenerate.examples.[A-Za-z]+@[0-9a-f]+" 17 | 18 | @Test 19 | fun `remove toString if not specified`() { 20 | val sampleDataClass = SampleDataClass("Sample") 21 | assertThat(sampleDataClass.toString()).matches(nativeToStringRegex) 22 | } 23 | 24 | @Test 25 | fun `do not remove toString if specified`() { 26 | val sampleDataClass = SampleDataClassToString("sample with toString") 27 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 28 | } 29 | 30 | @Test 31 | fun `do not remove equals and hashcode specified`() { 32 | val sampleDataClass1 = SampleDataClass("Sample") 33 | val sampleDataClass2 = SampleDataClass("Sample") 34 | 35 | assertThat(sampleDataClass1).isEqualTo(sampleDataClass2) 36 | assertThat(sampleDataClass1.hashCode()).isEqualTo(sampleDataClass2.hashCode()) 37 | } 38 | 39 | @Test 40 | fun `remove equals and hashcode if not specified`() { 41 | val sampleDataClass1 = SampleDataClassGenerationOff("Sample") 42 | val sampleDataClass2 = SampleDataClassGenerationOff("Sample") 43 | 44 | assertThat(sampleDataClass1).isNotEqualTo(sampleDataClass2) 45 | assertThat(sampleDataClass1.hashCode()).isNotEqualTo(sampleDataClass2.hashCode()) 46 | } 47 | 48 | @Test 49 | fun `do not remove equals and hashcode if specified`() { 50 | val sampleDataClass1 = SampleDataClassEqualsHashcode("Sample with equals and hashCode") 51 | val sampleDataClass2 = SampleDataClassEqualsHashcode("Sample with equals and hashCode") 52 | 53 | assertThat(sampleDataClass1).isEqualTo(sampleDataClass2) 54 | assertThat(sampleDataClass1.hashCode()).isEqualTo(sampleDataClass2.hashCode()) 55 | } 56 | 57 | @Test 58 | fun `multiple data class specifications works together`() { 59 | val sampleDataClass = SampleDataClassToStringEqualsHashcode(42, null) 60 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 61 | assertThat(sampleDataClass.copy()).isEqualTo(sampleDataClass) 62 | } 63 | 64 | /** TODO: T81258596 think about plugin customisation for all possible explicit override cases. */ 65 | @Test 66 | fun `respect explicit overrides`() { 67 | val sampleDataClass = SampleDataClassWithExplicitOverrides("Sample") 68 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 69 | 70 | assertThat(sampleDataClass.hashCode()).isEqualTo(42) 71 | assertThat(sampleDataClass).isEqualTo(null) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/implicit/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/examples/DataClassGenerateTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 9 | 10 | import org.assertj.core.api.Assertions.assertThat 11 | import org.junit.Test 12 | 13 | class DataClassGenerateTest { 14 | 15 | private val nativeToStringRegex = 16 | "com.facebook.kotlin.compilerplugins.dataclassgenerate.examples.[A-Za-z]+@[0-9a-f]+" 17 | 18 | @Test 19 | fun `remove toString if not specified`() { 20 | val sampleDataClass = SampleDataClass("Sample") 21 | assertThat(sampleDataClass.toString()).matches(nativeToStringRegex) 22 | } 23 | 24 | @Test 25 | fun `do not remove toString if specified`() { 26 | val sampleDataClass = SampleDataClassToString("sample with toString") 27 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 28 | } 29 | 30 | @Test 31 | fun `do not remove equals and hashcode specified`() { 32 | val sampleDataClass1 = SampleDataClass("Sample") 33 | val sampleDataClass2 = SampleDataClass("Sample") 34 | 35 | assertThat(sampleDataClass1).isEqualTo(sampleDataClass2) 36 | assertThat(sampleDataClass1.hashCode()).isEqualTo(sampleDataClass2.hashCode()) 37 | } 38 | 39 | @Test 40 | fun `remove equals and hashcode if not specified`() { 41 | val sampleDataClass1 = SampleDataClassGenerationOff("Sample") 42 | val sampleDataClass2 = SampleDataClassGenerationOff("Sample") 43 | 44 | assertThat(sampleDataClass1).isNotEqualTo(sampleDataClass2) 45 | assertThat(sampleDataClass1.hashCode()).isNotEqualTo(sampleDataClass2.hashCode()) 46 | } 47 | 48 | @Test 49 | fun `do not remove equals and hashcode if specified`() { 50 | val sampleDataClass1 = SampleDataClassEqualsHashcode("Sample with equals and hashCode") 51 | val sampleDataClass2 = SampleDataClassEqualsHashcode("Sample with equals and hashCode") 52 | 53 | assertThat(sampleDataClass1).isEqualTo(sampleDataClass2) 54 | assertThat(sampleDataClass1.hashCode()).isEqualTo(sampleDataClass2.hashCode()) 55 | } 56 | 57 | @Test 58 | fun `multiple data class specifications works together`() { 59 | val sampleDataClass = SampleDataClassToStringEqualsHashcode(42, null) 60 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 61 | assertThat(sampleDataClass.copy()).isEqualTo(sampleDataClass) 62 | } 63 | 64 | /** TODO: T81258596 think about plugin customisation for all possible explicit override cases. */ 65 | @Test 66 | fun `respect explicit overrides`() { 67 | val sampleDataClass = SampleDataClassWithExplicitOverrides("Sample") 68 | assertThat(sampleDataClass.toString()).doesNotMatch(nativeToStringRegex) 69 | 70 | assertThat(sampleDataClass.hashCode()).isEqualTo(42) 71 | assertThat(sampleDataClass).isEqualTo(null) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/misc/JavaDeclarationOriginExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate.misc 9 | 10 | import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor 11 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 12 | import org.jetbrains.kotlin.descriptors.ClassKind 13 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 14 | import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin 15 | import org.jetbrains.kotlin.ir.descriptors.IrBasedClassDescriptor 16 | import org.jetbrains.kotlin.ir.descriptors.IrBasedDeclarationDescriptor 17 | import org.jetbrains.kotlin.name.FqName 18 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull 19 | import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny 20 | import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin 21 | import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor 22 | 23 | private val compilerVersion: KotlinVersion = KotlinVersion.CURRENT 24 | private val kotlin150 = KotlinVersion(1, 5, 0) 25 | 26 | internal val JvmDeclarationOrigin.containingClassDescriptor: ClassDescriptor? 27 | get() = 28 | when { 29 | compilerVersion < kotlin150 -> descriptor?.containingDeclaration as? LazyClassDescriptor 30 | else -> descriptor?.containingDeclaration as? IrBasedClassDescriptor 31 | } 32 | 33 | internal fun JvmDeclarationOrigin.isDataClass(): Boolean = 34 | (containingClassDescriptor?.isData == true && 35 | containingClassDescriptor?.kind == ClassKind.CLASS) ?: false 36 | 37 | internal fun JvmDeclarationOrigin.superClassLiteral(): String { 38 | val superName = containingClassDescriptor?.getSuperClassNotAny()?.fqNameOrNull() 39 | return superName?.toString()?.replace(".", "/") ?: OBJECT_LITERAL 40 | } 41 | 42 | internal fun JvmDeclarationOrigin.isLikelyMethodOfSyntheticClass(): Boolean = 43 | containingClassDescriptor?.name?.asString()?.isLikelySynthetic() ?: false 44 | 45 | internal fun JvmDeclarationOrigin.findAnnotation(fqName: FqName): AnnotationDescriptor? = 46 | containingClassDescriptor?.annotations?.findAnnotation(fqName) 47 | 48 | internal fun JvmDeclarationOrigin.isSynthesized(): Boolean { 49 | return when { 50 | compilerVersion < kotlin150 -> { 51 | // Before Kotlin 1.5.0 it CallableMemberDescriptor::kind field was a reliable source 52 | (descriptor as? CallableMemberDescriptor)?.kind == CallableMemberDescriptor.Kind.SYNTHESIZED 53 | } 54 | else -> { 55 | // With JVM\IR it's no longer possible to check 56 | // IrBasedDeclarationDescriptor.kind == Kind.SYNTHESIZED, as field doesn't represent 57 | // a real situation with generated code 58 | (descriptor as? IrBasedDeclarationDescriptor<*>)?.owner?.origin == 59 | IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gradleplugin/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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /compiler/cli/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DcgTestCase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.CompilerConfigurationProperties 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.PluginMode 12 | import com.tschuchort.compiletesting.JvmCompilationResult 13 | import com.tschuchort.compiletesting.KotlinCompilation 14 | import com.tschuchort.compiletesting.PluginOption 15 | import com.tschuchort.compiletesting.SourceFile 16 | 17 | @OptIn(org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi::class) 18 | open class DcgTestCase { 19 | companion object { 20 | fun compileWithK2( 21 | vararg srcs: SourceFile, 22 | dcgConfig: DcgTestConfiguration = DEFAULT_DCG_CONFIG, 23 | ): JvmCompilationResult { 24 | return makeCompilationContext(srcs, dcgConfig).apply { supportsK2 = true }.compile() 25 | } 26 | 27 | fun compileWithK1( 28 | vararg srcs: SourceFile, 29 | dcgConfig: DcgTestConfiguration = DEFAULT_DCG_CONFIG, 30 | ): JvmCompilationResult { 31 | return makeCompilationContext(srcs, dcgConfig) 32 | .apply { 33 | languageVersion = "1.9" 34 | supportsK2 = false 35 | } 36 | .compile() 37 | } 38 | 39 | private fun makeCompilationContext( 40 | code: Array, 41 | dcgConfig: DcgTestConfiguration, 42 | ): KotlinCompilation = 43 | KotlinCompilation().apply { 44 | sources = code.asList() 45 | compilerPluginRegistrars = listOf(DataClassGenerateComponentRegistrar()) 46 | messageOutputStream = System.out 47 | commandLineProcessors = listOf(DataClassGenerateCommandLineProcessor()) 48 | pluginOptions = dcgConfig.asCliOptions() 49 | } 50 | 51 | val DEFAULT_DCG_CONFIG: DcgTestConfiguration = 52 | DcgTestConfiguration( 53 | enabled = true, 54 | pluginMode = PluginMode.EXPLICIT, 55 | generateSuperClass = true, 56 | ) 57 | } 58 | } 59 | 60 | data class DcgTestConfiguration( 61 | val enabled: Boolean, 62 | val pluginMode: PluginMode, 63 | val generateSuperClass: Boolean, 64 | ) { 65 | internal fun asCliOptions(): List { 66 | val pluginId = DataClassGenerateCommandLineProcessor().pluginId 67 | return listOf( 68 | PluginOption( 69 | pluginId, 70 | CompilerConfigurationProperties.ENABLED.cliOption.optionName, 71 | enabled.toString(), 72 | ), 73 | PluginOption( 74 | pluginId, 75 | CompilerConfigurationProperties.MODE.cliOption.optionName, 76 | pluginMode.name, 77 | ), 78 | PluginOption( 79 | pluginId, 80 | CompilerConfigurationProperties.GENERATE_SUPER_CLASS.cliOption.optionName, 81 | generateSuperClass.toString(), 82 | ), 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @REM Copyright (c) Meta Platforms, Inc. and affiliates. 2 | @REM 3 | @REM This source code is licensed under the MIT license found in the 4 | @REM LICENSE file in the root directory of this source tree. 5 | 6 | @rem 7 | @rem Copyright 2015 the original author or authors. 8 | @rem 9 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 10 | @rem you may not use this file except in compliance with the License. 11 | @rem You may obtain a copy of the License at 12 | @rem 13 | @rem https://www.apache.org/licenses/LICENSE-2.0 14 | @rem 15 | @rem Unless required by applicable law or agreed to in writing, software 16 | @rem distributed under the License is distributed on an "AS IS" BASIS, 17 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | @rem See the License for the specific language governing permissions and 19 | @rem limitations under the License. 20 | @rem 21 | @rem SPDX-License-Identifier: Apache-2.0 22 | @rem 23 | 24 | @if "%DEBUG%"=="" @echo off 25 | @rem ########################################################################## 26 | @rem 27 | @rem Gradle startup script for Windows 28 | @rem 29 | @rem ########################################################################## 30 | 31 | @rem Set local scope for the variables with windows NT shell 32 | if "%OS%"=="Windows_NT" setlocal 33 | 34 | set DIRNAME=%~dp0 35 | if "%DIRNAME%"=="" set DIRNAME=. 36 | @rem This is normally unused 37 | set APP_BASE_NAME=%~n0 38 | set APP_HOME=%DIRNAME% 39 | 40 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 41 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 42 | 43 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 44 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 45 | 46 | @rem Find java.exe 47 | if defined JAVA_HOME goto findJavaFromJavaHome 48 | 49 | set JAVA_EXE=java.exe 50 | %JAVA_EXE% -version >NUL 2>&1 51 | if %ERRORLEVEL% equ 0 goto execute 52 | 53 | echo. 1>&2 54 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 55 | echo. 1>&2 56 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 57 | echo location of your Java installation. 1>&2 58 | 59 | goto fail 60 | 61 | :findJavaFromJavaHome 62 | set JAVA_HOME=%JAVA_HOME:"=% 63 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 64 | 65 | if exist "%JAVA_EXE%" goto execute 66 | 67 | echo. 1>&2 68 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 69 | echo. 1>&2 70 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 71 | echo location of your Java installation. 1>&2 72 | 73 | goto fail 74 | 75 | :execute 76 | @rem Setup the command line 77 | 78 | set CLASSPATH= 79 | 80 | 81 | @rem Execute Gradle 82 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 83 | 84 | :end 85 | @rem End local scope for the variables with windows NT shell 86 | if %ERRORLEVEL% equ 0 goto mainEnd 87 | 88 | :fail 89 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 90 | rem the _cmd.exe /c_ return code! 91 | set EXIT_CODE=%ERRORLEVEL% 92 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 93 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 94 | exit /b %EXIT_CODE% 95 | 96 | :mainEnd 97 | if "%OS%"=="Windows_NT" endlocal 98 | 99 | :omega 100 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![DCG Logo](media/logo-full-color.svg#gh-light-mode-only) 2 | ![DCG Logo](media/logo-white.svg#gh-dark-mode-only) 3 | 4 | # DataClassGenerate 5 | _or simply DCG_ 6 | 7 | 8 | ## Motivation 9 | DataClassGenerate is a Kotlin compiler plugin that addresses APK size overhead. 10 | DCG brings data class’ app size contribution on-par with semantically identical Plain Java Objects. 11 | Kotlin positions data class as an easy-to-use and low friction utility for representing plain-old data. 12 | Despite appearing compact in source code, the compiler generates multiple utility methods for each data class, which impact APK size e.g.: `hashCode`, `equals`, `toString`, `copy`, `componentN`. 13 | Before an optimizer (like [Redex](https://github.com/facebook/redex/) or [R8](https://r8.googlesource.com/r8)) can strip these methods, it must prove that they are never used. 14 | Since methods like `toString`, `equals`, and `hashCode` are so fundamental (defined on `Object`/`Any`), calls to them will exist all over the app, and the optimizer must prove that a data class would not flow into any of those call sites, which is often not possible. 15 | 16 | ## How to use in your project 17 | 18 | Apply the gradle plugin. 19 | ```kotlin 20 | plugins { 21 | id("com.facebook.kotlin.compilerplugins.dataclassgenerate") version 22 | } 23 | ``` 24 | And that's it! The default configuration will add the artifact which contains `@DataClassGenerate` automatically. 25 | 26 | If needed, you can configure the DataClassGenerate plugin using `dataClassGenerate` block: 27 | ```kotlin 28 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.gradle.DataClassGenerateMode 29 | 30 | dataClassGenerate { 31 | enabled.set(true/false) // defaults to true 32 | mode.set(DataClassGenerateMode.EXPLICIT/STRICT/IMPLICIT) // defaults to EXPLICIT 33 | generateSuperClass.set(true/false) // defaults to false 34 | } 35 | ``` 36 | 37 | ## How it works 38 | DataClassGenerate Kotlin Compiler Plugin processes `@DataClassGenerate` annotation. 39 | 40 | ### `@DataClassGenerate` ANNOTATION 41 | _NOTE:_ DCG is applicable to Kotlin data classes only. 42 | 43 | _NOTE:_ DCG configures an intent to generate (KEEP) or skip (OMIT) `toString`, `equals`, and `hashCode` methods generation. 44 | 45 | _NOTE:_ DCG does not configure `copy`, `componentN`, and other methods, but [Redex](https://github.com/facebook/redex/) or R8 will take care of them. 46 | 47 | `@DataClassGenerate` annotation configures Kotlin data class code generation and bytecode optimizations. 48 | `@DataClassGenerate` is applicable only to Kotlin data classes, and will not make any change if applied to a regular class (*Unfortunately, Kotlin does not have a data class as an annotation target*). 49 | 50 | 51 | `@DataClassGenerate` annotation, together with applied DataClassGenerate compiler plugin, does 3 things: 52 | 1. Configures code generation of `toString` method. 53 | - `@DataClassGenerate(toString = Mode.KEEP)` will generate `toString` as in a usual data class. 54 | - `@DataClassGenerate(toString = Mode.OMIT)` will NOT generate `toString` for an annotated data class. 55 | 1. Configures code generation of `equals` and `hashCode` methods. 56 | - `@DataClassGenerate(equalsHashCode = Mode.KEEP)` will generate `equals` and `hashCode` as in usual data class. 57 | - `@DataClassGenerate(equalsHashCode = Mode.OMIT)` will NOT generate `equals` and `hashCode` for an annotated data class. 58 | 1. Adds a marker super class `DataClassSuper` for suitable Data Classes to make them available for Redex [Class Merging Optimization](https://github.com/facebook/redex/blob/main/docs/passes.md#classmergingpass). 59 | - Do not upcast data classes to `DataClassSuper`. There is no guarantee a concrete data class will get a marker super class. 60 | 61 | 62 | ### Generation modes 63 | 64 | How we can set `@DataClassGenerate (toString = ???, equalsHashCode = ???)` arguments? 65 | 66 | | Mode | Explanation | 67 | |-------|-------| 68 | | `KEEP` | Express an intent to keep/generate a method(s) | 69 | | `OMIT` | Express an intent to omit method(s) generation. | 70 | 71 | ### Annotation defaults 72 | `@DataClassGenerate` is declared with the following defaults: 73 | ```kotlin 74 | annotation class DataClassGenerate( 75 | val toString: Mode = Mode.OMIT, 76 | val equalsHashCode: Mode = Mode.KEEP 77 | ) 78 | ``` 79 | 80 | Kotlin allows annotation parameter name ommission. The table below explains `@DataClassGenerate` shortcuts: 81 | 82 | | Declaration | Explicit Equivalent | 83 | |-------|-------| 84 | |`@DataClassGenerate` | `@DataClassGenerate(toString = Mode.OMIT, equalsHashCode = Mode.KEEP)`| 85 | |`@DataClassGenerate()` | `@DataClassGenerate(toString = Mode.OMIT, equalsHashCode = Mode.KEEP)`| 86 | |`@DataClassGenerate(Mode.KEEP)` | `@DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP)`| 87 | |`@DataClassGenerate(Mode.KEEP, Mode.OMIT)` | `@DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.OMIT)`| 88 | |`@DataClassGenerate(equalsHashCode = Mode.OMIT)` | `@DataClassGenerate(toString = Mode.OMIT, equalsHashCode = Mode.OMIT)`| 89 | 90 | ## DCG MODES 91 | `DataClassGenerate` Kotlin compiler plugin works in multiple modes, but the most important is a `STRICT` mode. 92 | 93 | ### `STRICT` mode 94 | In `STRICT` mode: 95 | - Plugin applies only to data classes annotated with `@DataClassGenerate(...)`. 96 | - DCG will act following annotation instructions for method generation. 97 | - DCG will add marker super classes. 98 | 99 | - Plugin reports a compilation error whenever it sees a data class without `@DataClassGenerate(...)` annotation. 100 | 101 | Example: 102 | 103 | ``` 104 | // Plugin will generate: 105 | // -`toString` 106 | // -`equals`, and `hashCode` 107 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 108 | data class A(val i:Int) 109 | 110 | // Plugin will report a compilation error 111 | data class B(val l:Long) 112 | ``` 113 | 114 | ### `EXPLICIT` mode (current default) 115 | In `EXPLICIT` mode: 116 | - If a data class is annotated with `@DataClassGenerate`, DCG will act according to the annotation instructions for method generation. 117 | - If data class is NOT annotated with @DataClassGenerate, DCG will only add marker super classes (read previous section for details). 118 | 119 | Example: 120 | 121 | ``` 122 | // Plugin will generate: 123 | // -`toString` 124 | // -`equals`, and `hashCode` 125 | @DataClassGenerate(toString = Mode.KEEP, equalsHashCode = Mode.KEEP) 126 | data class A(val i:Int) 127 | 128 | // Plugin will only create a marker super class 129 | data class B(val l:Long) 130 | 131 | // Plugin will do nothing 132 | data class C(val l:Long): SomeSuperClass() 133 | ``` 134 | 135 | 136 | ## Releases 137 | 138 | Coming soon 139 | 140 | ## Compiler compatibility 141 | 142 | | K1 | K2 | 143 | |-----------|---------------------| 144 | | 1.5x | N/A | 145 | | 1.6x | N/A | 146 | | 1.7x-1.9x | 1.7.21 + `-Xuse-k2` | 147 | | N/A | 2.0+ | 148 | 149 | ## Public talks 150 | - [FOSDEM'22. DataClassGenerate. Shrinking Kotlin data classes](https://archive.fosdem.org/2022/schedule/event/dataclassgenerate_shrinking_kotlin_data_classes/) 151 | - [Droidcon'21. Kotlin Adoption at Scale](https://www.droidcon.com/2021/11/17/kotlin-adoption-at-scale/) 152 | 153 | ## Project team 154 | DCG was created by the Kotlin Foundation team @ Meta: 155 | - [Sergei Rybalkin](https://github.com/rybalkinsd/), 156 | - [Adrian Catana](https://github.com/adicatana/), 157 | - [Michal Zielinski](https://github.com/zielinskimz/), 158 | - [Hui Qin Ng](https://github.com/nghuiqin/), 159 | - [Nicola Corti](https://github.com/cortinico/) 160 | 161 | ## License 162 | DataClassGenerate is [MIT-licensed](https://github.com/facebookincubator/dataclassgenerate/blob/main/LICENSE). 163 | -------------------------------------------------------------------------------- /gradleplugin/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /media/logo-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/logo-black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /media/logo-full-color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /compiler/k1/src/main/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DataClassGenerateBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode 11 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.DataClassGenerateExt 12 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.configuration.PluginMode 13 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.OBJECT_LITERAL 14 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.findAnnotation 15 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.getValueArgument 16 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.isDataClass 17 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.isLikelyMethodOfSyntheticClass 18 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.isLikelySynthetic 19 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.isSynthesized 20 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.misc.superClassLiteral 21 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.visitor.EqualsMethodVisitor 22 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.visitor.HashCodeMethodVisitor 23 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.visitor.SuperClassInitCallOverrideVisitor 24 | import com.facebook.kotlin.compilerplugins.dataclassgenerate.visitor.ToStringMethodVisitor 25 | import org.jetbrains.kotlin.codegen.ClassBuilder 26 | import org.jetbrains.kotlin.codegen.DelegatingClassBuilder 27 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 28 | import org.jetbrains.kotlin.descriptors.isClass 29 | import org.jetbrains.kotlin.ir.descriptors.IrBasedClassDescriptor 30 | import org.jetbrains.kotlin.name.FqName 31 | import org.jetbrains.kotlin.psi.KtClass 32 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe 33 | import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin 34 | import org.jetbrains.org.objectweb.asm.MethodVisitor 35 | import org.jetbrains.org.objectweb.asm.signature.SignatureReader 36 | import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor 37 | import org.jetbrains.org.objectweb.asm.signature.SignatureWriter 38 | 39 | const val GENERATED_SUPER = 40 | "com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper" 41 | 42 | class DataClassGenerateBuilder( 43 | // As of Kotlin 1.7.21 K2 compiler supplies [null] as a [PsiElement] origin in [defineClass] 44 | // method 45 | // https://youtrack.jetbrains.com/issue/KT-56814 46 | private val declarationOrigin: JvmDeclarationOrigin, 47 | internal val classBuilder: ClassBuilder, 48 | private val mode: PluginMode, 49 | ) : DelegatingClassBuilder() { 50 | 51 | override fun getDelegate() = classBuilder 52 | 53 | override fun defineClass( 54 | // We don't rely on [PsiElement] here as in K2 it will be [null] in this DCG builder 55 | // We provide a corresponding data with a [declarationOrigin] field in this class. 56 | // Depending on https://youtrack.jetbrains.com/issue/KT-56814 resolution we should change 57 | // K2 behaviour 58 | _doNotUseOnlyPassToSuperCall: DcgPsiElement?, 59 | version: Int, 60 | access: Int, 61 | name: String, 62 | signature: String?, 63 | superName: String, 64 | interfaces: Array, 65 | ) { 66 | var effectiveSuperName = superName 67 | var effectiveSignature = signature 68 | // K1 handling 69 | val originElement = declarationOrigin.element 70 | if (originElement is KtClass) { 71 | if (originElement.isData()) { 72 | if (mode == PluginMode.STRICT && !originElement.isAnnotatedWithDataClassGenerate()) { 73 | throw DataClassGenerateStrictModeViolationException( 74 | generateStrictModeViolationMessage(originElement.fqName) 75 | ) 76 | } 77 | 78 | if (!name.isLikelySynthetic() && shouldOverrideSuperClass(superName)) { 79 | effectiveSuperName = GENERATED_SUPER 80 | effectiveSignature = rewriteSignature(signature) 81 | } 82 | } 83 | } 84 | // K2 handling 85 | else { 86 | val originElement = declarationOrigin.descriptor 87 | if (originElement is IrBasedClassDescriptor) { 88 | if (originElement.isData && originElement.kind.isClass) { 89 | if (mode == PluginMode.STRICT && !originElement.isAnnotatedWithDataClassGenerate()) { 90 | throw DataClassGenerateStrictModeViolationException( 91 | generateStrictModeViolationMessage(originElement.fqNameSafe) 92 | ) 93 | } 94 | 95 | if (!name.isLikelySynthetic() && shouldOverrideSuperClass(superName)) { 96 | effectiveSuperName = GENERATED_SUPER 97 | effectiveSignature = rewriteSignature(signature) 98 | } 99 | } 100 | } 101 | } 102 | 103 | super.defineClass( 104 | _doNotUseOnlyPassToSuperCall, 105 | version, 106 | access, 107 | name, 108 | effectiveSignature, 109 | effectiveSuperName, 110 | interfaces, 111 | ) 112 | } 113 | 114 | override fun newMethod( 115 | origin: JvmDeclarationOrigin, 116 | access: Int, 117 | name: String, 118 | desc: String, 119 | signature: String?, 120 | exceptions: Array?, 121 | ): MethodVisitor { 122 | val originalVisitor = super.newMethod(origin, access, name, desc, signature, exceptions) 123 | if (origin.isDataClass() && !origin.isLikelyMethodOfSyntheticClass()) { 124 | val superClassLiteral = origin.superClassLiteral() 125 | val shouldGenerateSuperclass = shouldOverrideSuperClass(superClassLiteral) 126 | 127 | // We should override call if we're generating a super class. 128 | if (name == INIT_METHOD_SIGNATURE) { 129 | return if (shouldGenerateSuperclass) SuperClassInitCallOverrideVisitor(originalVisitor) 130 | else originalVisitor 131 | } 132 | 133 | origin.findAnnotation(DataClassGenerateExt.annotationFqName).let { ad -> 134 | // We should not change methods if they were declared explicitly. 135 | if (origin.isSynthesized() && !shouldGenerate(name, ad)) { 136 | return when (name) { 137 | "equals" -> EqualsMethodVisitor(OBJECT_LITERAL, originalVisitor) 138 | "hashCode" -> HashCodeMethodVisitor(OBJECT_LITERAL, originalVisitor) 139 | "toString" -> ToStringMethodVisitor(OBJECT_LITERAL, originalVisitor) 140 | else -> originalVisitor 141 | } 142 | } 143 | } 144 | } 145 | 146 | return originalVisitor 147 | } 148 | 149 | fun shouldOverrideSuperClass(superClassLiteral: String): Boolean = 150 | DataClassGenerateExt.generateSuperClass && superClassLiteral == OBJECT_LITERAL 151 | 152 | private fun rewriteSignature(signature: String?): String? { 153 | // Note classes without generics have null signature. 154 | if (signature == null) { 155 | return null 156 | } 157 | 158 | val signatureReader = SignatureReader(signature) 159 | val signatureWriter = 160 | object : SignatureWriter() { 161 | var nextClassTypeIsSuperclass = false 162 | 163 | override fun visitSuperclass(): SignatureVisitor { 164 | nextClassTypeIsSuperclass = true 165 | return super.visitSuperclass() 166 | } 167 | 168 | override fun visitClassType(name: String) { 169 | var remappedName = name 170 | // Only remap the superclass, not any classes visited in formal types or interfaces. 171 | if (nextClassTypeIsSuperclass) { 172 | if (name == OBJECT_LITERAL) { 173 | remappedName = GENERATED_SUPER 174 | } 175 | nextClassTypeIsSuperclass = false 176 | } 177 | super.visitClassType(remappedName) 178 | } 179 | } 180 | signatureReader.accept(signatureWriter) 181 | return signatureWriter.toString() 182 | } 183 | 184 | private fun KtClass.isAnnotatedWithDataClassGenerate(): Boolean { 185 | return annotationEntries 186 | .map { it.shortName } 187 | .contains(DataClassGenerateExt.annotationFqName.shortName()) 188 | } 189 | 190 | private fun IrBasedClassDescriptor.isAnnotatedWithDataClassGenerate(): Boolean { 191 | return annotations.map { it.fqName }.contains(DataClassGenerateExt.annotationFqName) 192 | } 193 | 194 | private fun shouldGenerate( 195 | method: String, 196 | descriptor: AnnotationDescriptor?, 197 | ): Boolean { 198 | if (descriptor == null && mode != PluginMode.IMPLICIT) return true 199 | 200 | return when (method) { 201 | "equals", 202 | "hashCode" -> 203 | descriptor 204 | ?.getValueArgument(DataClassGenerateExt.eqHcAnnotationArg) 205 | .asBoolean(defaultIfParameterIsNotPassed = true) 206 | "toString" -> { 207 | descriptor 208 | ?.getValueArgument(DataClassGenerateExt.tsAnnotationArg) 209 | .asBoolean(defaultIfParameterIsNotPassed = false) 210 | } 211 | else -> true 212 | } 213 | } 214 | } 215 | 216 | fun generateStrictModeViolationMessage(fqName: FqName?): String = 217 | """ 218 | You are running DataClassGenerate compiler plugin in a STRICT mode. 219 | But $fqName is not annotated with @DataClassGenerate. 220 | 221 | Replace $fqName with @DataClassGenerate(toString=Mode.OMIT, equalsHashCode=Mode.KEEP) 222 | - If $fqName does not need a `toString()` method use `toString=Mode.OMIT` 223 | - If $fqName does not need `equals()` and hashCode()` use `equalsHashCode=Mode.OMIT` or 224 | consider replacing it with a regular class. 225 | 226 | Read more: 227 | 1. What is DataClassGenerate? - https://fburl.com/dataclassgenerate_wiki 228 | 2. How to configure @DataClassGenerate annotation? - https://fburl.com/dataclassgenerate 229 | 3. What is STRICT mode? - https://fburl.com/dataclassgenerate_mode 230 | """ 231 | .trimIndent() 232 | 233 | class DataClassGenerateStrictModeViolationException(message: String) : RuntimeException(message) 234 | 235 | private const val INIT_METHOD_SIGNATURE = "" 236 | -------------------------------------------------------------------------------- /compiler/cli/src/test/kotlin/com/facebook/kotlin/compilerplugins/dataclassgenerate/DcgCompilerIdentityTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.facebook.kotlin.compilerplugins.dataclassgenerate 9 | 10 | import com.tschuchort.compiletesting.SourceFile 11 | import org.assertj.core.api.Assertions.assertThat 12 | import org.junit.BeforeClass 13 | import org.junit.Test 14 | 15 | @OptIn(org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi::class) 16 | class DcgCompilerIdentityTest : DcgTestCase() { 17 | 18 | companion object { 19 | private lateinit var k1DcgMap: Map 20 | private lateinit var k2DcgMap: Map 21 | 22 | private val dataClasses = 23 | SourceFile.kotlin( 24 | "Container.kt", 25 | """ 26 | | package com.facebook.kotlin.compilerplugins.dataclassgenerate.examples 27 | | 28 | | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate 29 | | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.KEEP 30 | | import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode.OMIT 31 | | 32 | | data class NonAnnotatedDataClass(val str: String) 33 | | 34 | | @DataClassGenerate 35 | | data class SampleDataClassWithExplicitOverrides(val str: String) { 36 | | override fun toString() = "dummy" 37 | | override fun equals(other: Any?) = true 38 | | override fun hashCode() = 42 39 | | } 40 | | 41 | | @DataClassGenerate data class SampleDataClass(val str: String) 42 | | 43 | | @DataClassGenerate(OMIT, OMIT) data class SampleDataClassGenerationOff(val str: String) 44 | | 45 | | @DataClassGenerate(toString = KEEP) data class SampleDataClassToString(val str: String) 46 | | 47 | | @DataClassGenerate(equalsHashCode = KEEP) data class SampleDataClassEqualsHashcode(val str: String) 48 | | 49 | | @DataClassGenerate(KEEP, KEEP) 50 | | data class SampleDataClassToStringEqualsHashcode(val a: Int, val b: String?) 51 | | 52 | | open class Base 53 | | data class SampleWithSuper(val a: Int) : Base() 54 | """ 55 | .trimMargin(), 56 | ) 57 | 58 | @BeforeClass 59 | @JvmStatic 60 | fun compile() { 61 | val k1Compilation = compileWithK1(dataClasses, DCG_ANNOTATION) 62 | val k2Compilation = compileWithK2(dataClasses, DCG_ANNOTATION) 63 | k1DcgMap = k1Compilation.generatedFiles.asDcgMap() 64 | k2DcgMap = k2Compilation.generatedFiles.asDcgMap() 65 | } 66 | } 67 | 68 | @Test 69 | fun `non annotated data class`() { 70 | fun DcgDump.verify() { 71 | assertThat(superClass).isEqualTo(DCG_SUPER) 72 | assertThat(toStringDeclaration) 73 | .containsSubsequence( 74 | """ 75 | , INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 76 | """ 77 | .trimIndent() 78 | ) 79 | 80 | assertThat(hashCodeDeclaration) 81 | .containsSubsequence( 82 | """ 83 | , INVOKEVIRTUAL java/lang/String.hashCode ()I 84 | """ 85 | .trimIndent() 86 | ) 87 | 88 | assertThat(equalsDeclaration) 89 | .containsSubsequence( 90 | """ 91 | , INVOKESTATIC kotlin/jvm/internal/Intrinsics.areEqual (Ljava/lang/Object;Ljava/lang/Object;)Z 92 | """ 93 | .trimIndent() 94 | ) 95 | } 96 | 97 | with(k1DcgMap["NonAnnotatedDataClass.class"]!!) { verify() } 98 | with(k2DcgMap["NonAnnotatedDataClass.class"]!!) { verify() } 99 | } 100 | 101 | @Test 102 | fun `simple data class`() { 103 | fun DcgDump.verify() { 104 | assertThat(superClass).isEqualTo(DCG_SUPER) 105 | assertThat(toStringDeclaration) 106 | .containsSubsequence( 107 | """ 108 | , INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String; 109 | , ARETURN 110 | """ 111 | .trimIndent() 112 | ) 113 | 114 | assertThat(hashCodeDeclaration) 115 | .containsSubsequence( 116 | """ 117 | , INVOKEVIRTUAL java/lang/String.hashCode ()I 118 | """ 119 | .trimIndent() 120 | ) 121 | 122 | assertThat(equalsDeclaration) 123 | .containsSubsequence( 124 | """ 125 | , INVOKESTATIC kotlin/jvm/internal/Intrinsics.areEqual (Ljava/lang/Object;Ljava/lang/Object;)Z 126 | """ 127 | .trimIndent() 128 | ) 129 | } 130 | 131 | with(k1DcgMap["SampleDataClass.class"]!!) { verify() } 132 | with(k2DcgMap["SampleDataClass.class"]!!) { verify() } 133 | } 134 | 135 | @Test 136 | fun `simple data class without generation`() { 137 | fun DcgDump.verify() { 138 | assertThat(superClass).isEqualTo(DCG_SUPER) 139 | assertThat(toStringDeclaration) 140 | .containsSubsequence( 141 | """ 142 | , INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String; 143 | , ARETURN 144 | """ 145 | .trimIndent() 146 | ) 147 | 148 | assertThat(hashCodeDeclaration) 149 | .containsSubsequence( 150 | """ 151 | , INVOKESPECIAL java/lang/Object.hashCode ()I 152 | , IRETURN 153 | """ 154 | .trimIndent() 155 | ) 156 | 157 | assertThat(equalsDeclaration) 158 | .containsSubsequence( 159 | """ 160 | , INVOKESPECIAL java/lang/Object.equals (Ljava/lang/Object;)Z 161 | , IRETURN 162 | """ 163 | .trimIndent() 164 | ) 165 | } 166 | with(k1DcgMap["SampleDataClassGenerationOff.class"]!!) { verify() } 167 | with(k2DcgMap["SampleDataClassGenerationOff.class"]!!) { verify() } 168 | } 169 | 170 | @Test 171 | fun `simple data class with equals and hashCode`() { 172 | fun DcgDump.verify() { 173 | assertThat(superClass).isEqualTo(DCG_SUPER) 174 | assertThat(toStringDeclaration) 175 | .containsSubsequence( 176 | """ 177 | , INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 178 | """ 179 | .trimIndent() 180 | ) 181 | 182 | assertThat(hashCodeDeclaration) 183 | .containsSubsequence( 184 | """ 185 | , INVOKEVIRTUAL java/lang/String.hashCode ()I 186 | , IRETURN 187 | """ 188 | .trimIndent() 189 | ) 190 | 191 | assertThat(equalsDeclaration) 192 | .containsSubsequence( 193 | """ 194 | , INVOKESTATIC kotlin/jvm/internal/Intrinsics.areEqual (Ljava/lang/Object;Ljava/lang/Object;)Z 195 | """ 196 | .trimIndent() 197 | ) 198 | } 199 | 200 | with(k1DcgMap["SampleDataClassToString.class"]!!) { verify() } 201 | with(k2DcgMap["SampleDataClassToString.class"]!!) { verify() } 202 | } 203 | 204 | @Test 205 | fun `simple data class with toString`() { 206 | fun DcgDump.verify() { 207 | assertThat(superClass).isEqualTo(DCG_SUPER) 208 | assertThat(toStringDeclaration) 209 | .containsSubsequence( 210 | """ 211 | , INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String; 212 | , ARETURN 213 | """ 214 | .trimIndent() 215 | ) 216 | 217 | assertThat(hashCodeDeclaration) 218 | .containsSubsequence( 219 | """ 220 | , INVOKEVIRTUAL java/lang/String.hashCode ()I 221 | , IRETURN 222 | """ 223 | .trimIndent() 224 | ) 225 | 226 | assertThat(equalsDeclaration) 227 | .containsSubsequence( 228 | """ 229 | , INVOKESTATIC kotlin/jvm/internal/Intrinsics.areEqual (Ljava/lang/Object;Ljava/lang/Object;)Z 230 | """ 231 | .trimIndent() 232 | ) 233 | } 234 | 235 | with(k1DcgMap["SampleDataClassEqualsHashcode.class"]!!) { verify() } 236 | with(k2DcgMap["SampleDataClassEqualsHashcode.class"]!!) { verify() } 237 | } 238 | 239 | @Test 240 | fun `simple data class with toString, equals, and hashCode`() { 241 | 242 | fun DcgDump.verify() { 243 | assertThat(superClass).isEqualTo(DCG_SUPER) 244 | assertThat(toStringDeclaration) 245 | .containsSubsequence( 246 | """ 247 | , INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 248 | """ 249 | .trimIndent() 250 | ) 251 | 252 | assertThat(hashCodeDeclaration) 253 | .containsSubsequence( 254 | """ 255 | , INVOKEVIRTUAL java/lang/String.hashCode ()I 256 | """ 257 | .trimIndent() 258 | ) 259 | 260 | assertThat(equalsDeclaration) 261 | .containsSubsequence( 262 | """ 263 | , INVOKESTATIC kotlin/jvm/internal/Intrinsics.areEqual (Ljava/lang/Object;Ljava/lang/Object;)Z 264 | """ 265 | .trimIndent() 266 | ) 267 | } 268 | with(k1DcgMap["SampleDataClassToStringEqualsHashcode.class"]!!) { verify() } 269 | with(k2DcgMap["SampleDataClassToStringEqualsHashcode.class"]!!) { verify() } 270 | } 271 | 272 | @Test 273 | fun `class with an existing super`() { 274 | fun DcgDump.verify() { 275 | assertThat(superClass).isNotEqualTo(DCG_SUPER) 276 | } 277 | 278 | with(k1DcgMap["SampleWithSuper.class"]!!) { verify() } 279 | with(k2DcgMap["SampleWithSuper.class"]!!) { verify() } 280 | } 281 | } 282 | 283 | private val DCG_ANNOTATION = 284 | SourceFile.kotlin( 285 | "DataClassGenerate.kt", 286 | """ 287 | | package com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation 288 | | 289 | | enum class Mode { 290 | | KEEP, 291 | | OMIT 292 | | } 293 | | 294 | | @Target(AnnotationTarget.CLASS) 295 | | 296 | | annotation class DataClassGenerate( 297 | | @get:JvmName("toString_uniqueJvmName") val toString: Mode = Mode.OMIT, 298 | | val equalsHashCode: Mode = Mode.KEEP 299 | | ) 300 | """ 301 | .trimMargin(), 302 | ) 303 | 304 | private const val DCG_SUPER = 305 | "com/facebook/kotlin/compilerplugins/dataclassgenerate/superclass/DataClassSuper" 306 | --------------------------------------------------------------------------------