mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 07:59:21 +08:00
Switch to kotlin-compile-testing for JsonClassCodegenProcessorTe… (#928)
* Add kotlin-compile-testing dependency * Add Okio dependency to tests to match kotlin-compile-testing Without this, Okio.sink() fails to resolve at runtime * Try converting privateConstructor() to kotlin-compile-testing * Space * Update to 1.2.2 * Use new kotlin source file API * Use temporaryFolder as workingDir * Extract prepareCompilation helper method This allows for modifying the compilation with extra bits as needed * Migrate tests entirely to new API * Remove incorrect error message This passed before, but was picked up in https://github.com/square/moshi/pull/903 before and observed to be incorrect. * Remove custom kotlin compiler implementations * Add an OK exit code test This wasn't possible before! * Remove jitpack dependency now that it's on mavencentral * Move versions to parent pom * Fix new test from rebase
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
<dependency>
|
||||
<groupId>com.squareup</groupId>
|
||||
<artifactId>kotlinpoet</artifactId>
|
||||
<version>1.3.0</version>
|
||||
<version>${kotlinpoet.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ltgt.gradle.incap</groupId>
|
||||
@@ -37,12 +37,12 @@
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
<version>0.10</version>
|
||||
<version>${auto-common.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.service</groupId>
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<version>${autoservice.version}</version>
|
||||
<version>${auto-service.version}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -56,32 +56,31 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.truth</groupId>
|
||||
<artifactId>truth</artifactId>
|
||||
<version>${truth.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
The Kotlin compiler must be near the end of the list because its .jar file includes an
|
||||
The Kotlin compiler usage must be near the end of the list because its .jar file includes an
|
||||
obsolete version of Guava!
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-compiler-embeddable</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-annotation-processing-embeddable</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.eugeniomarletti.kotlin.metadata</groupId>
|
||||
<artifactId>kotlin-metadata</artifactId>
|
||||
</dependency>
|
||||
<!--
|
||||
Though we don't use compile-testing, including it is a convenient way to get tools.jar on the
|
||||
classpath. This dependency is required by kapt3.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.google.testing.compile</groupId>
|
||||
<artifactId>compile-testing</artifactId>
|
||||
<groupId>com.github.tschuchortdev</groupId>
|
||||
<artifactId>kotlin-compile-testing</artifactId>
|
||||
<version>${kotlin-compile-testing.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
<version>${okio2.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -111,7 +110,7 @@
|
||||
<annotationProcessorPath>
|
||||
<groupId>com.google.auto.service</groupId>
|
||||
<artifactId>auto-service</artifactId>
|
||||
<version>${autoservice.version}</version>
|
||||
<version>${auto-service.version}</version>
|
||||
</annotationProcessorPath>
|
||||
<annotationProcessorPath>
|
||||
<groupId>net.ltgt.gradle.incap</groupId>
|
||||
|
@@ -15,384 +15,352 @@
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen
|
||||
|
||||
import com.tschuchort.compiletesting.KotlinCompilation
|
||||
import com.tschuchort.compiletesting.SourceFile
|
||||
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import javax.annotation.processing.Processor
|
||||
|
||||
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
||||
class JsonClassCodegenProcessorTest {
|
||||
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test fun privateConstructor() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class PrivateConstructor private constructor(var a: Int, var b: Int) {
|
||||
| fun a() = a
|
||||
| fun b() = b
|
||||
| companion object {
|
||||
| fun newInstance(a: Int, b: Int) = PrivateConstructor(a, b)
|
||||
| }
|
||||
|}
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("constructor is not internal or public")
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PrivateConstructor private constructor(var a: Int, var b: Int) {
|
||||
fun a() = a
|
||||
fun b() = b
|
||||
companion object {
|
||||
fun newInstance(a: Int, b: Int) = PrivateConstructor(a, b)
|
||||
}
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("constructor is not internal or public")
|
||||
}
|
||||
|
||||
@Test fun privateConstructorParameter() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class PrivateConstructorParameter(private var a: Int)
|
||||
|""".trimMargin())
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("property a is not visible")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PrivateConstructorParameter(private var a: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("property a is not visible")
|
||||
}
|
||||
|
||||
@Test fun privateProperties() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class PrivateProperties {
|
||||
| private var a: Int = -1
|
||||
| private var b: Int = -1
|
||||
|}
|
||||
|""".trimMargin())
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("property a is not visible")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PrivateProperties {
|
||||
private var a: Int = -1
|
||||
private var b: Int = -1
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("property a is not visible")
|
||||
}
|
||||
|
||||
@Test fun interfacesNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|interface Interface
|
||||
|""".trimMargin())
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
@JsonClass(generateAdapter = true)
|
||||
interface Interface
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to Interface: must be a Kotlin class")
|
||||
}
|
||||
|
||||
@Test fun abstractClassesNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|abstract class AbstractClass(val a: Int)
|
||||
|""".trimMargin())
|
||||
@Test fun interfacesDoNotErrorWhenGeneratorNotSet() {
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
@JsonClass(generateAdapter = true, generator="customGenerator")
|
||||
interface Interface
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
|
||||
}
|
||||
|
||||
@Test fun abstractClassesNotSupported() {
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
abstract class AbstractClass(val a: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to AbstractClass: must not be abstract")
|
||||
}
|
||||
|
||||
@Test fun sealedClassesNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|sealed class SealedClass(val a: Int)
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
sealed class SealedClass(val a: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to SealedClass: must not be sealed")
|
||||
}
|
||||
|
||||
@Test fun innerClassesNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|class Outer {
|
||||
| @JsonClass(generateAdapter = true)
|
||||
| inner class InnerClass(val a: Int)
|
||||
|}
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
class Outer {
|
||||
@JsonClass(generateAdapter = true)
|
||||
inner class InnerClass(val a: Int)
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to Outer.InnerClass: must not be an inner class")
|
||||
}
|
||||
|
||||
@Test fun enumClassesNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|enum class KotlinEnum {
|
||||
| A, B
|
||||
|}
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
enum class KotlinEnum {
|
||||
A, B
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass with 'generateAdapter = \"true\"' can't be applied to KotlinEnum: code gen for enums is not supported or necessary")
|
||||
}
|
||||
|
||||
// Annotation processors don't get called for local classes, so we don't have the opportunity to
|
||||
// print an error message. Instead local classes will fail at runtime.
|
||||
@Ignore
|
||||
@Test fun localClassesNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|fun outer() {
|
||||
| @JsonClass(generateAdapter = true)
|
||||
| class LocalClass(val a: Int)
|
||||
|}
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
@Ignore @Test fun localClassesNotSupported() {
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
fun outer() {
|
||||
@JsonClass(generateAdapter = true)
|
||||
class LocalClass(val a: Int)
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to LocalClass: must not be local")
|
||||
}
|
||||
|
||||
@Test fun objectDeclarationsNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|object ObjectDeclaration {
|
||||
| var a = 5
|
||||
|}
|
||||
|""".trimMargin())
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
object ObjectDeclaration {
|
||||
var a = 5
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to ObjectDeclaration: must be a Kotlin class")
|
||||
}
|
||||
|
||||
@Test fun objectExpressionsNotSupported() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|val expression = object : Any() {
|
||||
| var a = 5
|
||||
|}
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
val expression = object : Any() {
|
||||
var a = 5
|
||||
}
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: @JsonClass can't be applied to expression\$annotations(): must be a Kotlin class")
|
||||
}
|
||||
|
||||
@Test fun requiredTransientConstructorParameterFails() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class RequiredTransientConstructorParameter(@Transient var a: Int)
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class RequiredTransientConstructorParameter(@Transient var a: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No default value for transient property a")
|
||||
}
|
||||
|
||||
@Test fun nonPropertyConstructorParameter() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class NonPropertyConstructorParameter(a: Int, val b: Int)
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
@JsonClass(generateAdapter = true)
|
||||
class NonPropertyConstructorParameter(a: Int, val b: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains(
|
||||
"error: No property for required constructor parameter a")
|
||||
}
|
||||
|
||||
@Test fun badGeneratedAnnotation() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.kaptArgs[JsonClassCodegenProcessor.OPTION_GENERATED] = "javax.annotation.GeneratedBlerg"
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|data class Foo(val a: Int)
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains(
|
||||
val result = prepareCompilation(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Foo(val a: Int)
|
||||
"""
|
||||
)).apply {
|
||||
kaptArgs[JsonClassCodegenProcessor.OPTION_GENERATED] = "javax.annotation.GeneratedBlerg"
|
||||
}.compile()
|
||||
assertThat(result.messages).contains(
|
||||
"Invalid option value for ${JsonClassCodegenProcessor.OPTION_GENERATED}")
|
||||
}
|
||||
|
||||
@Test fun multipleErrors() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class Class1(private var a: Int, private var b: Int)
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class Class2(private var c: Int)
|
||||
|""".trimMargin())
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("property a is not visible")
|
||||
assertThat(result.systemErr).contains("property b is not visible")
|
||||
assertThat(result.systemErr).contains("property c is not visible")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class Class1(private var a: Int, private var b: Int)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class Class2(private var c: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("property a is not visible")
|
||||
assertThat(result.messages).contains("property c is not visible")
|
||||
}
|
||||
|
||||
@Test fun extendPlatformType() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|import java.util.Date
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class ExtendsPlatformClass(var a: Int) : Date()
|
||||
|""".trimMargin())
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.util.Date
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("supertype java.util.Date is not a Kotlin type")
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ExtendsPlatformClass(var a: Int) : Date()
|
||||
"""
|
||||
))
|
||||
assertThat(result.messages).contains("supertype java.util.Date is not a Kotlin type")
|
||||
}
|
||||
|
||||
@Test fun extendJavaType() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|import com.squareup.moshi.kotlin.codegen.JavaSuperclass
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class ExtendsJavaType(var b: Int) : JavaSuperclass()
|
||||
|""".trimMargin())
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr)
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.kotlin.codegen.JavaSuperclass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ExtendsJavaType(var b: Int) : JavaSuperclass()
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages)
|
||||
.contains("supertype com.squareup.moshi.kotlin.codegen.JavaSuperclass is not a Kotlin type")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonFieldApplicableQualifier() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|import com.squareup.moshi.JsonQualifier
|
||||
|import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
|import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
|import kotlin.annotation.Retention
|
||||
|import kotlin.annotation.Target
|
||||
|
|
||||
|@Retention(RUNTIME)
|
||||
|@Target(PROPERTY)
|
||||
|@JsonQualifier
|
||||
|annotation class UpperCase
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class ClassWithQualifier(@UpperCase val a: Int)
|
||||
|""".trimMargin())
|
||||
@Test fun nonFieldApplicableQualifier() {
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
val result = call.execute()
|
||||
println(result.systemErr)
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("JsonQualifier @UpperCase must support FIELD target")
|
||||
@Retention(RUNTIME)
|
||||
@Target(PROPERTY)
|
||||
@JsonQualifier
|
||||
annotation class UpperCase
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ClassWithQualifier(@UpperCase val a: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("JsonQualifier @UpperCase must support FIELD target")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonRuntimeQualifier() {
|
||||
val call = KotlinCompilerCall(temporaryFolder.root)
|
||||
call.inheritClasspath = true
|
||||
call.addService(Processor::class, JsonClassCodegenProcessor::class)
|
||||
call.addKt("source.kt", """
|
||||
|import com.squareup.moshi.JsonClass
|
||||
|import com.squareup.moshi.JsonQualifier
|
||||
|import kotlin.annotation.AnnotationRetention.BINARY
|
||||
|import kotlin.annotation.AnnotationTarget.FIELD
|
||||
|import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
|import kotlin.annotation.Retention
|
||||
|import kotlin.annotation.Target
|
||||
|
|
||||
|@Retention(BINARY)
|
||||
|@Target(PROPERTY, FIELD)
|
||||
|@JsonQualifier
|
||||
|annotation class UpperCase
|
||||
|
|
||||
|@JsonClass(generateAdapter = true)
|
||||
|class ClassWithQualifier(@UpperCase val a: Int)
|
||||
|""".trimMargin())
|
||||
@Test fun nonRuntimeQualifier() {
|
||||
val result = compile(kotlin("source.kt",
|
||||
"""
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import kotlin.annotation.AnnotationRetention.BINARY
|
||||
import kotlin.annotation.AnnotationTarget.FIELD
|
||||
import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
val result = call.execute()
|
||||
assertThat(result.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.systemErr).contains("JsonQualifier @UpperCase must have RUNTIME retention")
|
||||
@Retention(BINARY)
|
||||
@Target(PROPERTY, FIELD)
|
||||
@JsonQualifier
|
||||
annotation class UpperCase
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class ClassWithQualifier(@UpperCase val a: Int)
|
||||
"""
|
||||
))
|
||||
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
|
||||
assertThat(result.messages).contains("JsonQualifier @UpperCase must have RUNTIME retention")
|
||||
}
|
||||
|
||||
private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
|
||||
return KotlinCompilation()
|
||||
.apply {
|
||||
workingDir = temporaryFolder.root
|
||||
annotationProcessors = listOf(JsonClassCodegenProcessor())
|
||||
inheritClassPath = true
|
||||
sources = sourceFiles.asList()
|
||||
verbose = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun compile(vararg sourceFiles: SourceFile): KotlinCompilation.Result {
|
||||
return prepareCompilation(*sourceFiles).compile()
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap
|
||||
import okio.Buffer
|
||||
import okio.Okio
|
||||
import org.jetbrains.kotlin.cli.common.CLITool
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.PrintStream
|
||||
import java.net.URLClassLoader
|
||||
import java.net.URLDecoder
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/** Prepares an invocation of the Kotlin compiler. */
|
||||
class KotlinCompilerCall(var scratchDir: File) {
|
||||
val sourcesDir = File(scratchDir, "sources")
|
||||
val classesDir = File(scratchDir, "classes")
|
||||
val servicesJar = File(scratchDir, "services.jar")
|
||||
|
||||
var inheritClasspath = false
|
||||
|
||||
val args = mutableListOf<String>()
|
||||
val kaptArgs = mutableMapOf<String, String>()
|
||||
val classpath = mutableListOf<String>()
|
||||
val services = LinkedHashMultimap.create<KClass<*>, KClass<*>>()
|
||||
|
||||
/** Adds a source file to be compiled. */
|
||||
fun addKt(path: String, source: String) {
|
||||
val sourceFile = File(sourcesDir, path)
|
||||
sourceFile.parentFile.mkdirs()
|
||||
Okio.buffer(Okio.sink(sourceFile)).use {
|
||||
it.writeUtf8(source)
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a service like an annotation processor to make available to the compiler. */
|
||||
fun addService(serviceClass: KClass<*>, implementation: KClass<*>) {
|
||||
services.put(serviceClass, implementation)
|
||||
}
|
||||
|
||||
fun execute(): KotlinCompilerResult {
|
||||
val fullArgs = mutableListOf<String>()
|
||||
fullArgs.addAll(args)
|
||||
|
||||
fullArgs.add("-d")
|
||||
fullArgs.add(classesDir.toString())
|
||||
|
||||
val fullClasspath = fullClasspath()
|
||||
if (fullClasspath.isNotEmpty()) {
|
||||
fullArgs.add("-classpath")
|
||||
fullArgs.add(fullClasspath.joinToString(separator = ":"))
|
||||
}
|
||||
|
||||
for (source in sourcesDir.listFiles()) {
|
||||
fullArgs.add(source.toString())
|
||||
}
|
||||
|
||||
fullArgs.addAll(annotationProcessorArgs())
|
||||
if (kaptArgs.isNotEmpty()) {
|
||||
fullArgs.apply {
|
||||
add("-P")
|
||||
add("plugin:org.jetbrains.kotlin.kapt3:apoptions=${encodeOptions(kaptArgs)}")
|
||||
}
|
||||
}
|
||||
|
||||
val systemErrBuffer = Buffer()
|
||||
val oldSystemErr = System.err
|
||||
System.setErr(PrintStream(systemErrBuffer.outputStream()))
|
||||
try {
|
||||
val exitCode = CLITool.doMainNoExit(K2JVMCompiler(), fullArgs.toTypedArray())
|
||||
val systemErr = systemErrBuffer.readUtf8()
|
||||
return KotlinCompilerResult(systemErr, exitCode)
|
||||
} finally {
|
||||
System.setErr(oldSystemErr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns arguments necessary to enable and configure kapt3. */
|
||||
private fun annotationProcessorArgs(): List<String> {
|
||||
val kaptSourceDir = File(scratchDir, "kapt/sources")
|
||||
val kaptStubsDir = File(scratchDir, "kapt/stubs")
|
||||
|
||||
return listOf(
|
||||
"-Xplugin=${kapt3Jar()}",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:sources=$kaptSourceDir",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:classes=$classesDir",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptStubsDir",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:apclasspath=$servicesJar",
|
||||
"-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns the classpath to use when compiling code. */
|
||||
private fun fullClasspath(): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
result.addAll(classpath)
|
||||
|
||||
// Copy over the classpath of the running application.
|
||||
if (inheritClasspath) {
|
||||
for (classpathFile in classpathFiles()) {
|
||||
result.add(classpathFile.toString())
|
||||
}
|
||||
}
|
||||
|
||||
if (!services.isEmpty) {
|
||||
writeServicesJar()
|
||||
result.add(servicesJar.toString())
|
||||
}
|
||||
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a .jar file that holds ServiceManager registrations. Necessary because AutoService's
|
||||
* results might not be visible to this test.
|
||||
*/
|
||||
private fun writeServicesJar() {
|
||||
ZipOutputStream(FileOutputStream(servicesJar)).use { zipOutputStream ->
|
||||
for (entry in services.asMap()) {
|
||||
zipOutputStream.putNextEntry(
|
||||
ZipEntry("META-INF/services/${entry.key.qualifiedName}"))
|
||||
val serviceFile = Okio.buffer(Okio.sink(zipOutputStream))
|
||||
for (implementation in entry.value) {
|
||||
serviceFile.writeUtf8(implementation.qualifiedName!!)
|
||||
serviceFile.writeUtf8("\n")
|
||||
}
|
||||
serviceFile.emit() // Don't close the entry; that closes the file.
|
||||
zipOutputStream.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the files on the host process' classpath. */
|
||||
private fun classpathFiles(): List<File> {
|
||||
val classLoader = JsonClassCodegenProcessorTest::class.java.classLoader
|
||||
if (classLoader !is URLClassLoader) {
|
||||
throw UnsupportedOperationException("unable to extract classpath from $classLoader")
|
||||
}
|
||||
|
||||
val result = mutableListOf<File>()
|
||||
for (url in classLoader.urLs) {
|
||||
if (url.protocol != "file") {
|
||||
throw UnsupportedOperationException("unable to handle classpath element $url")
|
||||
}
|
||||
result.add(File(URLDecoder.decode(url.path, "UTF-8")))
|
||||
}
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
/** Returns the path to the kotlin-annotation-processing .jar file. */
|
||||
private fun kapt3Jar(): File {
|
||||
for (file in classpathFiles()) {
|
||||
if (file.name.startsWith("kotlin-annotation-processing-embeddable")) return file
|
||||
}
|
||||
throw IllegalStateException("no kotlin-annotation-processing-embeddable jar on classpath:\n " +
|
||||
"${classpathFiles().joinToString(separator = "\n ")}}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encodes a mapping of annotation processor args for kapt, as specified by
|
||||
* https://kotlinlang.org/docs/reference/kapt.html#apjavac-options-encoding
|
||||
*/
|
||||
private fun encodeOptions(options: Map<String, String>): String {
|
||||
val buffer = Buffer()
|
||||
ObjectOutputStream(buffer.outputStream()).use { oos ->
|
||||
oos.writeInt(options.size)
|
||||
for ((key, value) in options.entries) {
|
||||
oos.writeUTF(key)
|
||||
oos.writeUTF(value)
|
||||
}
|
||||
}
|
||||
return buffer.readByteString().base64()
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi.kotlin.codegen
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
|
||||
class KotlinCompilerResult(
|
||||
val systemErr: String,
|
||||
var exitCode: ExitCode
|
||||
)
|
19
pom.xml
19
pom.xml
@@ -29,20 +29,25 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.7</java.version>
|
||||
<kotlin.version>1.3.40</kotlin.version>
|
||||
<kotlin-metadata.version>1.4.0</kotlin-metadata.version>
|
||||
<dokka.version>0.9.17</dokka.version>
|
||||
<maven-assembly.version>3.1.0</maven-assembly.version>
|
||||
<incap.version>0.2</incap.version>
|
||||
<autoservice.version>1.0-rc5</autoservice.version>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<auto-common.version>0.10</auto-common.version>
|
||||
<auto-service.version>1.0-rc5</auto-service.version>
|
||||
<dokka.version>0.9.17</dokka.version>
|
||||
<incap.version>0.2</incap.version>
|
||||
<okio.version>1.16.0</okio.version>
|
||||
<okio2.version>2.1.0</okio2.version>
|
||||
<kotlin.version>1.3.40</kotlin.version>
|
||||
<kotlinpoet.version>1.3.0</kotlinpoet.version>
|
||||
<kotlin-metadata.version>1.4.0</kotlin-metadata.version>
|
||||
<maven-assembly.version>3.1.0</maven-assembly.version>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<assertj.version>3.11.1</assertj.version>
|
||||
<compile-testing.version>0.15</compile-testing.version>
|
||||
<junit.version>4.12</junit.version>
|
||||
<assertj.version>3.11.1</assertj.version>
|
||||
<kotlin-compile-testing.version>1.2.2</kotlin-compile-testing.version>
|
||||
<truth.version>1.0</truth.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
|
Reference in New Issue
Block a user