diff --git a/kotlin/codegen/pom.xml b/kotlin/codegen/pom.xml index 21bda8e..81d75ee 100644 --- a/kotlin/codegen/pom.xml +++ b/kotlin/codegen/pom.xml @@ -27,6 +27,26 @@ kotlinpoet ${kotlinpoet.version} + + org.jetbrains.kotlinx + kotlinx-metadata-jvm + 0.1.0 + + + com.squareup + kotlinpoet-metadata + ${kotlinpoet.version} + + + com.squareup + kotlinpoet-metadata-specs + ${kotlinpoet.version} + + + com.squareup + kotlinpoet-classinspector-elements + ${kotlinpoet.version} + net.ltgt.gradle.incap incap @@ -34,11 +54,6 @@ provided true - - com.google.auto - auto-common - ${auto-common.version} - com.google.auto.service auto-service-annotations @@ -67,10 +82,6 @@ The Kotlin compiler usage must be near the end of the list because its .jar file includes an obsolete version of Guava! --> - - me.eugeniomarletti.kotlin.metadata - kotlin-metadata - com.github.tschuchortdev kotlin-compile-testing @@ -135,6 +146,12 @@ + + + + -Xuse-experimental=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview + + org.apache.maven.plugins diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AppliedType.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AppliedType.kt index a3e27e9..91d9de2 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AppliedType.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AppliedType.kt @@ -15,10 +15,6 @@ */ package com.squareup.moshi.kotlin.codegen -import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.TypeVariableName -import com.squareup.kotlinpoet.asTypeName -import com.squareup.moshi.kotlin.codegen.api.TypeResolver import javax.lang.model.element.TypeElement import javax.lang.model.type.DeclaredType import javax.lang.model.util.Types @@ -29,7 +25,6 @@ import javax.lang.model.util.Types */ internal class AppliedType private constructor( val element: TypeElement, - val resolver: TypeResolver, private val mirror: DeclaredType ) { /** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */ @@ -41,32 +36,17 @@ internal class AppliedType private constructor( for (supertype in types.directSupertypes(mirror)) { val supertypeDeclaredType = supertype as DeclaredType val supertypeElement = supertypeDeclaredType.asElement() as TypeElement - val appliedSupertype = AppliedType(supertypeElement, - resolver(supertypeElement, supertypeDeclaredType), supertypeDeclaredType) + val appliedSupertype = AppliedType(supertypeElement, supertypeDeclaredType) appliedSupertype.supertypes(types, result) } return result } - /** Returns a resolver that uses `element` and `mirror` to resolve type parameters. */ - private fun resolver(element: TypeElement, mirror: DeclaredType): TypeResolver { - return object : TypeResolver() { - override fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName { - val index = element.typeParameters.indexOfFirst { - it.simpleName.toString() == typeVariable.name - } - check(index != -1) { "Unexpected type variable $typeVariable in $mirror" } - val argument = mirror.typeArguments[index] - return argument.asTypeName() - } - } - } - override fun toString() = mirror.toString() companion object { fun get(typeElement: TypeElement): AppliedType { - return AppliedType(typeElement, TypeResolver(), typeElement.asType() as DeclaredType) + return AppliedType(typeElement, typeElement.asType() as DeclaredType) } } -} \ No newline at end of file +} diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt index 7517696..a5e19f8 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt @@ -16,22 +16,25 @@ package com.squareup.moshi.kotlin.codegen import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview import com.squareup.moshi.JsonClass import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator -import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils -import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor import net.ltgt.gradle.incap.IncrementalAnnotationProcessor import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Filer +import javax.annotation.processing.Messager import javax.annotation.processing.ProcessingEnvironment import javax.annotation.processing.Processor import javax.annotation.processing.RoundEnvironment import javax.lang.model.SourceVersion -import javax.lang.model.element.Element import javax.lang.model.element.TypeElement -import javax.lang.model.element.VariableElement -import javax.tools.Diagnostic.Kind.ERROR +import javax.lang.model.util.Elements +import javax.lang.model.util.Types +import javax.tools.Diagnostic /** * An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them. @@ -40,13 +43,11 @@ import javax.tools.Diagnostic.Kind.ERROR * * The generated class will match the visibility of the given data class (i.e. if it's internal, the * adapter will also be internal). - * - * If you define a companion object, a jsonAdapter() extension function will be generated onto it. - * If you don't want this though, you can use the runtime [JsonClass] factory implementation. */ +@KotlinPoetMetadataPreview @AutoService(Processor::class) @IncrementalAnnotationProcessor(ISOLATING) -class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { +class JsonClassCodegenProcessor : AbstractProcessor() { companion object { /** @@ -65,6 +66,10 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils ) } + private lateinit var types: Types + private lateinit var elements: Elements + private lateinit var filer: Filer + private lateinit var messager: Messager private val annotation = JsonClass::class.java private var generatedType: TypeElement? = null @@ -83,16 +88,37 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils } processingEnv.elementUtils.getTypeElement(it) } + this.types = processingEnv.typeUtils + this.elements = processingEnv.elementUtils + this.filer = processingEnv.filer + this.messager = processingEnv.messager } override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { for (type in roundEnv.getElementsAnnotatedWith(annotation)) { + if (type !is TypeElement) { + messager.printMessage( + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $type: must be a Kotlin class", + type) + continue + } val jsonClass = type.getAnnotation(annotation) if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) { val generator = adapterGenerator(type) ?: continue generator - .generateFile(generatedType?.asClassName()) { + .generateFile { it.toBuilder() + .apply { + generatedType?.asClassName()?.let { generatedClassName -> + addAnnotation( + AnnotationSpec.builder(generatedClassName) + .addMember("value = [%S]", + JsonClassCodegenProcessor::class.java.canonicalName) + .addMember("comments = %S", "https://github.com/square/moshi") + .build() + ) + } + } .addOriginatingElement(type) .build() } @@ -103,12 +129,12 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils return false } - private fun adapterGenerator(element: Element): AdapterGenerator? { - val type = targetType(messager, elementUtils, typeUtils, element) ?: return null + private fun adapterGenerator(element: TypeElement): AdapterGenerator? { + val type = targetType(messager, elements, types, element) ?: return null val properties = mutableMapOf() for (property in type.properties.values) { - val generator = property.generator(messager) + val generator = property.generator(messager, element, elements) if (generator != null) { properties[property.name] = generator } @@ -117,7 +143,9 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils for ((name, parameter) in type.constructor.parameters) { if (type.properties[parameter.name] == null && !parameter.hasDefault) { messager.printMessage( - ERROR, "No property for required constructor parameter $name", parameter.tag()) + Diagnostic.Kind.ERROR, + "No property for required constructor parameter $name", + element) return null } } diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt index 90b884f..3ea13ed 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/AdapterGenerator.kt @@ -17,7 +17,6 @@ package com.squareup.moshi.kotlin.codegen.api import com.squareup.kotlinpoet.ARRAY import com.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -36,7 +35,6 @@ import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.Moshi import com.squareup.moshi.internal.Util -import com.squareup.moshi.kotlin.codegen.JsonClassCodegenProcessor import java.lang.reflect.Constructor import java.lang.reflect.Type @@ -83,7 +81,8 @@ internal class AdapterGenerator( nameAllocator.newName("options"), JsonReader.Options::class.asTypeName(), KModifier.PRIVATE) .initializer("%T.of(${nonTransientProperties.joinToString(", ") { - CodeBlock.of("%S", it.jsonName).toString() + // We manually put in quotes because we know the jsonName is already escaped + CodeBlock.of("\"%L\"", it.jsonName).toString() }})", JsonReader.Options::class.asTypeName()) .build() @@ -96,27 +95,20 @@ internal class AdapterGenerator( .initializer("null") .build() - fun generateFile(generatedOption: ClassName?, typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec { + fun generateFile(typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec { for (property in nonTransientProperties) { property.allocateNames(nameAllocator) } val result = FileSpec.builder(className.packageName, adapterName) result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.") - result.addType(generateType(generatedOption).let(typeHook)) + result.addType(generateType().let(typeHook)) return result.build() } - private fun generateType(generatedOption: ClassName?): TypeSpec { + private fun generateType(): TypeSpec { val result = TypeSpec.classBuilder(adapterName) - generatedOption?.let { - result.addAnnotation(AnnotationSpec.builder(it) - .addMember("value = [%S]", JsonClassCodegenProcessor::class.java.canonicalName) - .addMember("comments = %S", "https://github.com/square/moshi") - .build()) - } - result.superclass(jsonAdapterTypeName) if (typeVariables.isNotEmpty()) { @@ -366,7 +358,8 @@ internal class AdapterGenerator( result.addStatement("%N.beginObject()", writerParam) nonTransientProperties.forEach { property -> - result.addStatement("%N.name(%S)", writerParam, property.jsonName) + // We manually put in quotes because we know the jsonName is already escaped + result.addStatement("%N.name(\"%L\")", writerParam, property.jsonName) result.addStatement("%N.toJson(%N, %N.%N)", nameAllocator[property.delegateKey], writerParam, valueParam, property.name) } diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt index 9c07707..b9e10bd 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt @@ -16,7 +16,6 @@ package com.squareup.moshi.kotlin.codegen.api import com.squareup.kotlinpoet.AnnotationSpec -import kotlin.reflect.KClass /** A parameter in user code that should be populated by generated code. */ internal data class TargetParameter( @@ -24,15 +23,5 @@ internal data class TargetParameter( val index: Int, val hasDefault: Boolean, val jsonName: String = name, - val qualifiers: Set? = null, - private val tags: Map, Any> -) { - /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */ - fun tag(type: KClass): T? { - @Suppress("UNCHECKED_CAST") - return tags[type] as T? - } - - /** Returns the tag attached with [T] as a key, or null if no tag is attached with that key. */ - inline fun tag(): T? = tag(T::class) -} + val qualifiers: Set? = null +) diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt index 20c5958..1c141d4 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt @@ -15,23 +15,19 @@ */ package com.squareup.moshi.kotlin.codegen.api -import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName /** A property in user code that maps to JSON. */ internal data class TargetProperty( - val name: String, - val type: TypeName, + val propertySpec: PropertySpec, val parameter: TargetParameter?, - val annotationHolder: FunSpec?, - val field: PropertySpec?, - val setter: FunSpec?, - val getter: FunSpec?, val visibility: KModifier, val jsonName: String ) { + val name: String get() = propertySpec.name + val type: TypeName get() = propertySpec.type val parameterIndex get() = parameter?.index ?: -1 val hasDefault get() = parameter?.hasDefault ?: true diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeResolver.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeResolver.kt deleted file mode 100644 index c956702..0000000 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeResolver.kt +++ /dev/null @@ -1,63 +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.api - -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.ParameterizedTypeName -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.TypeVariableName -import com.squareup.kotlinpoet.WildcardTypeName - -/** - * Resolves type parameters against a type declaration. Use this to fill in type variables with - * their actual type parameters. - */ -open class TypeResolver { - open fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName = typeVariable - - fun resolve(typeName: TypeName): TypeName { - return when (typeName) { - is ClassName -> typeName - - is ParameterizedTypeName -> { - typeName.rawType.parameterizedBy(*(typeName.typeArguments.map { resolve(it) }.toTypedArray())) - .copy(nullable = typeName.isNullable) - } - - is WildcardTypeName -> { - when { - typeName.inTypes.size == 1 -> { - WildcardTypeName.consumerOf(resolve(typeName.inTypes[0])) - .copy(nullable = typeName.isNullable) - } - typeName.outTypes.size == 1 -> { - WildcardTypeName.producerOf(resolve(typeName.outTypes[0])) - .copy(nullable = typeName.isNullable) - } - else -> { - throw IllegalArgumentException( - "Unrepresentable wildcard type. Cannot have more than one bound: $typeName") - } - } - } - - is TypeVariableName -> resolveTypeVariable(typeName) - - else -> throw IllegalArgumentException("Unrepresentable type: $typeName") - } - } -} diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt index 6d87af5..ef1b832 100644 --- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt +++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/metadata.kt @@ -15,26 +15,23 @@ */ package com.squareup.moshi.kotlin.codegen -import com.google.auto.common.AnnotationMirrors -import com.google.auto.common.MoreTypes import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.CodeBlock -import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier -import com.squareup.kotlinpoet.KModifier.PUBLIC -import com.squareup.kotlinpoet.KModifier.VARARG -import com.squareup.kotlinpoet.ParameterSpec -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.STAR -import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.TypeVariableName -import com.squareup.kotlinpoet.WildcardTypeName +import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName -import com.squareup.kotlinpoet.asTypeVariableName -import com.squareup.kotlinpoet.tag +import com.squareup.kotlinpoet.classinspector.elements.ElementsClassInspector +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import com.squareup.kotlinpoet.metadata.isAbstract +import com.squareup.kotlinpoet.metadata.isClass +import com.squareup.kotlinpoet.metadata.isEnum +import com.squareup.kotlinpoet.metadata.isInner +import com.squareup.kotlinpoet.metadata.isLocal +import com.squareup.kotlinpoet.metadata.isSealed +import com.squareup.kotlinpoet.metadata.specs.ClassInspector +import com.squareup.kotlinpoet.metadata.specs.toTypeSpec +import com.squareup.kotlinpoet.metadata.toImmutableKmClass import com.squareup.moshi.Json import com.squareup.moshi.JsonQualifier import com.squareup.moshi.kotlin.codegen.api.DelegateKey @@ -43,264 +40,125 @@ import com.squareup.moshi.kotlin.codegen.api.TargetConstructor import com.squareup.moshi.kotlin.codegen.api.TargetParameter import com.squareup.moshi.kotlin.codegen.api.TargetProperty import com.squareup.moshi.kotlin.codegen.api.TargetType -import com.squareup.moshi.kotlin.codegen.api.TypeResolver -import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata -import me.eugeniomarletti.kotlin.metadata.KotlinMetadata -import me.eugeniomarletti.kotlin.metadata.classKind -import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue -import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull -import me.eugeniomarletti.kotlin.metadata.isDataClass -import me.eugeniomarletti.kotlin.metadata.isInnerClass -import me.eugeniomarletti.kotlin.metadata.isPrimary -import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature -import me.eugeniomarletti.kotlin.metadata.kotlinMetadata -import me.eugeniomarletti.kotlin.metadata.modality -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Class -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.ABSTRACT -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.SEALED -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Type -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter.Variance -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.LOCAL -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PRIVATE -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PRIVATE_TO_THIS -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PROTECTED -import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver -import me.eugeniomarletti.kotlin.metadata.shadow.util.capitalizeDecapitalize.decapitalizeAsciiOnly -import me.eugeniomarletti.kotlin.metadata.visibility import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import java.lang.annotation.Target import javax.annotation.processing.Messager -import javax.lang.model.element.AnnotationMirror -import javax.lang.model.element.Element import javax.lang.model.element.ElementKind -import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.Modifier -import javax.lang.model.element.Modifier.DEFAULT -import javax.lang.model.element.Modifier.FINAL -import javax.lang.model.element.Modifier.NATIVE -import javax.lang.model.element.Modifier.STATIC -import javax.lang.model.element.Modifier.SYNCHRONIZED -import javax.lang.model.element.Modifier.TRANSIENT -import javax.lang.model.element.Modifier.VOLATILE import javax.lang.model.element.TypeElement -import javax.lang.model.element.VariableElement -import javax.lang.model.type.TypeVariable import javax.lang.model.util.Elements import javax.lang.model.util.Types import javax.tools.Diagnostic -import javax.tools.Diagnostic.Kind.ERROR -import kotlin.reflect.KClass -private fun TypeParameter.asTypeName( - nameResolver: NameResolver, - getTypeParameter: (index: Int) -> TypeParameter, - resolveAliases: Boolean = false -): TypeVariableName { - val possibleBounds = upperBoundList.map { - it.asTypeName(nameResolver, getTypeParameter, resolveAliases) - } - return if (possibleBounds.isEmpty()) { - TypeVariableName( - name = nameResolver.getString(name), - variance = variance.asKModifier()) - } else { - TypeVariableName( - name = nameResolver.getString(name), - bounds = *possibleBounds.toTypedArray(), - variance = variance.asKModifier()) - } +private val JSON_QUALIFIER = JsonQualifier::class.java +private val JSON = Json::class.asClassName() +private val OBJECT_CLASS = ClassName("java.lang", "Object") +private val VISIBILITY_MODIFIERS = setOf( + KModifier.INTERNAL, + KModifier.PRIVATE, + KModifier.PROTECTED, + KModifier.PUBLIC +) + +private fun Collection.visibility(): KModifier { + return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC } -private fun TypeParameter.Variance.asKModifier(): KModifier? { - return when (this) { - Variance.IN -> KModifier.IN - Variance.OUT -> KModifier.OUT - Variance.INV -> null - } -} - -private fun Visibility?.asKModifier(): KModifier { - return when (this) { - INTERNAL -> KModifier.INTERNAL - PRIVATE -> KModifier.PRIVATE - PROTECTED -> KModifier.PROTECTED - Visibility.PUBLIC -> KModifier.PUBLIC - PRIVATE_TO_THIS -> KModifier.PRIVATE - LOCAL -> KModifier.PRIVATE - else -> PUBLIC - } -} - -/** - * Returns the TypeName of this type as it would be seen in the source code, including nullability - * and generic type parameters. - * - * @param [nameResolver] a [NameResolver] instance from the source proto - * @param [getTypeParameter] a function that returns the type parameter for the given index. **Only - * called if [ProtoBuf.Type.hasTypeParameter] is true!** - */ -private fun Type.asTypeName( - nameResolver: NameResolver, - getTypeParameter: (index: Int) -> TypeParameter, - useAbbreviatedType: Boolean = true -): TypeName { - - val argumentList = when { - useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.argumentList - else -> argumentList - } - - if (hasFlexibleUpperBound()) { - return WildcardTypeName.producerOf( - flexibleUpperBound.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)) - .copy(nullable = nullable) - } else if (hasOuterType()) { - return WildcardTypeName.consumerOf( - outerType.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)) - .copy(nullable = nullable) - } - - val realType = when { - hasTypeParameter() -> return getTypeParameter(typeParameter) - .asTypeName(nameResolver, getTypeParameter, useAbbreviatedType) - .copy(nullable = nullable) - hasTypeParameterName() -> typeParameterName - useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.typeAliasName - else -> className - } - - var typeName: TypeName = - ClassName.bestGuess(nameResolver.getString(realType) - .replace("/", ".")) - - if (argumentList.isNotEmpty()) { - val remappedArgs: Array = argumentList.map { argumentType -> - val nullableProjection = if (argumentType.hasProjection()) { - argumentType.projection - } else null - if (argumentType.hasType()) { - argumentType.type.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType) - .let { argumentTypeName -> - nullableProjection?.let { projection -> - when (projection) { - Type.Argument.Projection.IN -> WildcardTypeName.consumerOf(argumentTypeName) - Type.Argument.Projection.OUT -> WildcardTypeName.producerOf(argumentTypeName) - Type.Argument.Projection.STAR -> STAR - Type.Argument.Projection.INV -> TODO("INV projection is unsupported") - } - } ?: argumentTypeName - } - } else { - STAR - } - }.toTypedArray() - typeName = (typeName as ClassName).parameterizedBy(*remappedArgs) - } - - return typeName.copy(nullable = nullable) -} - -internal fun primaryConstructor(metadata: KotlinClassMetadata, - elements: Elements): TargetConstructor { - val (nameResolver, classProto) = metadata.data - - // TODO allow custom constructor - val proto = classProto.constructorList - .single { it.isPrimary } - val constructorJvmSignature = proto.getJvmConstructorSignature( - nameResolver, classProto.typeTable) - val element = classProto.fqName - .let(nameResolver::getString) - .replace('/', '.') - .let(elements::getTypeElement) - .enclosedElements - .mapNotNull { - it.takeIf { it.kind == ElementKind.CONSTRUCTOR }?.let { it as ExecutableElement } - } - .first() - // TODO Temporary until JVM method signature matching is better - // .single { it.jvmMethodSignature == constructorJvmSignature } +@KotlinPoetMetadataPreview +internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): TargetConstructor? { + val primaryConstructor = kotlinApi.primaryConstructor ?: return null val parameters = mutableMapOf() - for (parameter in proto.valueParameterList) { - val name = nameResolver.getString(parameter.name) - val index = proto.valueParameterList.indexOf(parameter) - val paramElement = element.parameters[index] + for ((index, parameter) in primaryConstructor.parameters.withIndex()) { + val name = parameter.name parameters[name] = TargetParameter( name = name, index = index, - hasDefault = parameter.declaresDefaultValue, - qualifiers = paramElement.qualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec), - jsonName = paramElement.jsonName ?: name, - tags = mapOf(VariableElement::class to paramElement) + hasDefault = parameter.defaultValue != null, + qualifiers = parameter.annotations.qualifiers(elements), + jsonName = parameter.annotations.jsonName() ?: name.escapeDollarSigns() ) } - return TargetConstructor(parameters, - proto.visibility.asKModifier()) + return TargetConstructor(parameters, primaryConstructor.modifiers.visibility()) } -private val OBJECT_CLASS = ClassName("java.lang", "Object") - /** Returns a target type for `element`, or null if it cannot be used with code gen. */ +@KotlinPoetMetadataPreview internal fun targetType(messager: Messager, elements: Elements, types: Types, - element: Element): TargetType? { - val typeMetadata: KotlinMetadata? = element.kotlinMetadata - if (element !is TypeElement || typeMetadata !is KotlinClassMetadata) { + element: TypeElement): TargetType? { + val typeMetadata = element.getAnnotation(Metadata::class.java) + if (typeMetadata == null) { messager.printMessage( - ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element) + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", + element) + return null + } + + val kmClass = try { + typeMetadata.toImmutableKmClass() + } catch (e: UnsupportedOperationException) { + messager.printMessage( + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Class type", + element) return null } - val proto = typeMetadata.data.classProto when { - proto.classKind == Class.Kind.ENUM_CLASS -> { + kmClass.isEnum -> { messager.printMessage( - ERROR, + Diagnostic.Kind.ERROR, "@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary", element) return null } - proto.classKind != Class.Kind.CLASS -> { + !kmClass.isClass -> { messager.printMessage( - ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element) + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", + element) return null } - proto.isInnerClass -> { + kmClass.isInner -> { messager.printMessage( - ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element) + Diagnostic.Kind.ERROR, + "@JsonClass can't be applied to $element: must not be an inner class", element) return null } - proto.modality == SEALED -> { + kmClass.isSealed -> { messager.printMessage( - ERROR, "@JsonClass can't be applied to $element: must not be sealed", element) + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be sealed", element) return null } - proto.modality == ABSTRACT -> { + kmClass.isAbstract -> { messager.printMessage( - ERROR, "@JsonClass can't be applied to $element: must not be abstract", element) + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be abstract", + element) return null } - proto.visibility == LOCAL -> { + kmClass.isLocal -> { messager.printMessage( - ERROR, "@JsonClass can't be applied to $element: must not be local", element) + Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be local", + element) return null } } - val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver) + val elementHandler = ElementsClassInspector.create(elements, types) + val kotlinApi = kmClass.toTypeSpec(elementHandler) + val typeVariables = kotlinApi.typeVariables val appliedType = AppliedType.get(element) - val constructor = primaryConstructor(typeMetadata, elements) + val constructor = primaryConstructor(kotlinApi, elements) + if (constructor == null) { + messager.printMessage(Diagnostic.Kind.ERROR, "No primary constructor found on $element", + element) + return null + } if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) { - messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " + + messager.printMessage(Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: " + "primary constructor is not internal or public", element) return null } @@ -313,14 +171,19 @@ internal fun targetType(messager: Messager, if (supertype.element.kind != ElementKind.CLASS) { continue // Don't load properties for interface types. } - if (supertype.element.kotlinMetadata == null) { - messager.printMessage(ERROR, + if (supertype.element.getAnnotation(Metadata::class.java) == null) { + messager.printMessage(Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type", element) return null } - val supertypeProperties = declaredProperties( - supertype.element, supertype.resolver, constructor) + val supertypeProperties = if (supertype.element == element) { + // We've already parsed this api above, reuse it + declaredProperties(supertype.element, constructor, elementHandler, kotlinApi) + } else { + declaredProperties( + supertype.element, constructor, elementHandler) + } for ((name, property) in supertypeProperties) { properties.putIfAbsent(name, property) } @@ -330,224 +193,37 @@ internal fun targetType(messager: Messager, constructor = constructor, properties = properties, typeVariables = typeVariables, - isDataClass = proto.isDataClass, - visibility = proto.visibility.asKModifier()) + isDataClass = KModifier.DATA in kotlinApi.modifiers, + visibility = kotlinApi.modifiers.visibility()) } /** Returns the properties declared by `typeElement`. */ +@KotlinPoetMetadataPreview private fun declaredProperties( typeElement: TypeElement, - typeResolver: TypeResolver, - constructor: TargetConstructor + constructor: TargetConstructor, + elementHandler: ClassInspector, + kotlinApi: TypeSpec = typeElement.toTypeSpec(elementHandler) ): Map { - val typeMetadata: KotlinClassMetadata = typeElement.kotlinMetadata as KotlinClassMetadata - val nameResolver = typeMetadata.data.nameResolver - val classProto = typeMetadata.data.classProto - - val annotationHolders = mutableMapOf() - val fields = mutableMapOf() - val setters = mutableMapOf() - val getters = mutableMapOf() - for (element in typeElement.enclosedElements) { - if (element is VariableElement) { - fields[element.name] = element - } else if (element is ExecutableElement) { - when { - element.name.startsWith("get") -> { - val name = element.name.substring("get".length).decapitalizeAsciiOnly() - getters[name] = element - } - element.name.startsWith("is") -> { - val name = element.name.substring("is".length).decapitalizeAsciiOnly() - getters[name] = element - } - element.name.startsWith("set") -> { - val name = element.name.substring("set".length).decapitalizeAsciiOnly() - setters[name] = element - } - } - - val propertyProto = typeMetadata.data.getPropertyOrNull(element) - if (propertyProto != null) { - val name = nameResolver.getString(propertyProto.name) - annotationHolders[name] = element - } - } - } val result = mutableMapOf() - for (property in classProto.propertyList) { - val name = nameResolver.getString(property.name) - val type = typeResolver.resolve(property.returnType.asTypeName( - nameResolver, classProto::getTypeParameter, false)) + for (property in kotlinApi.propertySpecs) { + val name = property.name val parameter = constructor.parameters[name] - val fieldElement = fields[name] - val annotationHolder = annotationHolders[name] - // Used for setter/getter/is lookups. Guaranteed to be safe because kotlin doesn't allow you to - // have both "AAA" and "aAA". - val decapitalizedName = name.decapitalizeAsciiOnly() - result[name] = TargetProperty(name = name, - type = type, + result[name] = TargetProperty( + propertySpec = property, parameter = parameter, - annotationHolder = annotationHolder?.asFunSpec(), - field = fieldElement?.asPropertySpec(), - setter = setters[decapitalizedName]?.asFunSpec(), - getter = getters[decapitalizedName]?.asFunSpec(), - visibility = property.visibility.asKModifier(), - jsonName = jsonName( - fieldElement = fieldElement, - parameter = parameter, - annotationHolder = annotationHolder, - name = name - ) + visibility = property.modifiers.visibility(), + jsonName = parameter?.jsonName ?: property.annotations.jsonName() + ?: name.escapeDollarSigns() ) } return result } -/** Returns the @Json name of this property, or this property's name if none is provided. */ -private fun jsonName( - fieldElement: Element?, - parameter: TargetParameter?, - annotationHolder: ExecutableElement?, - name: String): String { - val fieldJsonName = fieldElement.jsonName - val annotationHolderJsonName = annotationHolder.jsonName - val parameterJsonName = parameter?.jsonName - - return when { - fieldJsonName != null -> fieldJsonName - annotationHolderJsonName != null -> annotationHolderJsonName - parameterJsonName != null -> parameterJsonName - else -> name - } -} - -private val Element.name get() = simpleName.toString() - -private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List { - return proto.typeParameterList.map { typeParameter -> - val possibleBounds = typeParameter.upperBoundList - .map { it.asTypeName(nameResolver, proto::getTypeParameter, false) } - val typeVar = if (possibleBounds.isEmpty()) { - TypeVariableName( - name = nameResolver.getString(typeParameter.name), - variance = typeParameter.varianceModifier) - } else { - TypeVariableName( - name = nameResolver.getString(typeParameter.name), - bounds = *possibleBounds.toTypedArray(), - variance = typeParameter.varianceModifier) - } - return@map typeVar.copy(reified = typeParameter.reified) - } -} - -private val TypeParameter.varianceModifier: KModifier? - get() { - return variance.asKModifier().let { - // We don't redeclare out variance here - if (it == KModifier.OUT) { - null - } else { - it - } - } - } - -/** - * Returns a new [PropertySpec] representation of [this]. - * - * This will copy its name, type, visibility modifiers, constant value, and annotations. Note that - * Java modifiers that correspond to annotations in kotlin will be added as well (`volatile`, - * `transient`, etc`. - * - * The original `field` ([this]) is stored in [PropertySpec.tag]. - */ -internal fun VariableElement.asPropertySpec(asJvmField: Boolean = false): PropertySpec { - require(kind == ElementKind.FIELD) { - "Must be a field!" - } - val modifiers: Set = modifiers - val fieldName = simpleName.toString() - val propertyBuilder = PropertySpec.builder(fieldName, asType().asTypeName()) - propertyBuilder.addModifiers(*modifiers.mapNotNull { it.asKModifier() }.toTypedArray()) - constantValue?.let { - if (it is String) { - propertyBuilder.initializer(CodeBlock.of("%S", it)) - } else { - propertyBuilder.initializer(CodeBlock.of("%L", it)) - } - } - propertyBuilder.addAnnotations(annotationMirrors.map(AnnotationMirror::asAnnotationSpec)) - propertyBuilder.addAnnotations(modifiers.mapNotNull { it.asAnnotation() }) - propertyBuilder.tag(this) - if (asJvmField && KModifier.PRIVATE !in propertyBuilder.modifiers) { - propertyBuilder.addAnnotation(JvmField::class) - } - return propertyBuilder.build() -} - -/** - * Returns a new [AnnotationSpec] representation of [this]. - * - * Identical and delegates to [AnnotationSpec.get], but the original `mirror` is also stored - * in [AnnotationSpec.tag]. - */ -internal fun AnnotationMirror.asAnnotationSpec(): AnnotationSpec { - return AnnotationSpec.get(this) - .toBuilder() - .tag(MoreTypes.asTypeElement(annotationType)) - .build() -} - -/** - * Returns a new [FunSpec] representation of [this]. - * - * This will copy its visibility modifiers, type parameters, return type, name, parameters, and - * throws declarations. - * - * The original `method` ([this]) is stored in [FunSpec.tag]. - * - * Nearly identical to [FunSpec.overriding], but no override modifier is added nor are checks around - * overridability done - */ -internal fun ExecutableElement.asFunSpec(): FunSpec { - var modifiers: Set = modifiers - val methodName = simpleName.toString() - val funBuilder = FunSpec.builder(methodName) - - modifiers = modifiers.toMutableSet() - funBuilder.jvmModifiers(modifiers) - - typeParameters - .map { it.asType() as TypeVariable } - .map { it.asTypeVariableName() } - .forEach { funBuilder.addTypeVariable(it) } - - funBuilder.returns(returnType.asTypeName()) - funBuilder.addParameters(ParameterSpec.parametersOf(this)) - if (isVarArgs) { - funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last() - .toBuilder() - .addModifiers(VARARG) - .build() - } - - if (thrownTypes.isNotEmpty()) { - val throwsValueString = thrownTypes.joinToString { "%T::class" } - funBuilder.addAnnotation(AnnotationSpec.builder(Throws::class) - .addMember(throwsValueString, *thrownTypes.toTypedArray()) - .build()) - } - - funBuilder.tag(this) - return funBuilder.build() -} - -private val TargetProperty.isTransient get() = field != null && field.annotations.any { it.className == Transient::class.asClassName() } -private val TargetProperty.isSettable get() = setter != null || parameter != null +private val TargetProperty.isTransient get() = propertySpec.annotations.any { it.className == Transient::class.asClassName() } +private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null private val TargetProperty.isVisible: Boolean get() { return visibility == KModifier.INTERNAL @@ -559,26 +235,24 @@ private val TargetProperty.isVisible: Boolean * Returns a generator for this property, or null if either there is an error and this property * cannot be used with code gen, or if no codegen is necessary for this property. */ -internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? { - val element = field?.tag() ?: setter?.tag() - ?: getter!!.tag() +internal fun TargetProperty.generator( + messager: Messager, + sourceElement: TypeElement, + elements: Elements +): PropertyGenerator? { if (isTransient) { if (!hasDefault) { - element?.let { - messager.printMessage( - Diagnostic.Kind.ERROR, "No default value for transient property ${this}", - it) - } + messager.printMessage( + Diagnostic.Kind.ERROR, "No default value for transient property $name", + sourceElement) return null } return PropertyGenerator(this, DelegateKey(type, emptyList()), true) } if (!isVisible) { - element?.let { - messager.printMessage(Diagnostic.Kind.ERROR, "property ${this} is not visible", - it) - } + messager.printMessage(Diagnostic.Kind.ERROR, "property $name is not visible", + sourceElement) return null } @@ -586,10 +260,12 @@ internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? { return null // This property is not settable. Ignore it. } - val jsonQualifierMirrors = jsonQualifiers(element) - for (jsonQualifier in jsonQualifierMirrors) { + // Merge parameter and property annotations + val qualifiers = parameter?.qualifiers.orEmpty() + propertySpec.annotations.qualifiers(elements) + for (jsonQualifier in qualifiers) { // Check Java types since that covers both Java and Kotlin annotations. - val annotationElement = jsonQualifier.tag() ?: continue + val annotationElement = elements.getTypeElement(jsonQualifier.className.canonicalName) + ?: continue annotationElement.getAnnotation(Retention::class.java)?.let { if (it.value != RetentionPolicy.RUNTIME) { messager.printMessage(Diagnostic.Kind.ERROR, @@ -604,7 +280,7 @@ internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? { } } - val jsonQualifierSpecs = jsonQualifierMirrors.map { + val jsonQualifierSpecs = qualifiers.map { it.toBuilder() .useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD) .build() @@ -614,57 +290,21 @@ internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? { DelegateKey(type, jsonQualifierSpecs)) } -/** Returns the JsonQualifiers on the field and parameter of this property. */ -private fun TargetProperty.jsonQualifiers(element: Element?): Set { - val elementQualifiers = element.qualifiers - val annotationHolderQualifiers = annotationHolder?.tag().qualifiers - val parameterQualifiers = parameter?.qualifiers.orEmpty() - - // TODO(jwilson): union the qualifiers somehow? - return when { - elementQualifiers.isNotEmpty() -> elementQualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec) - annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers.mapTo( - mutableSetOf(), AnnotationMirror::asAnnotationSpec) - parameterQualifiers.isNotEmpty() -> parameterQualifiers - else -> setOf() +private fun List?.qualifiers(elements: Elements): Set { + if (this == null) return setOf() + return filterTo(mutableSetOf()) { + elements.getTypeElement(it.className.toString()).getAnnotation(JSON_QUALIFIER) != null } } -private fun Modifier.asKModifier(): KModifier? { - return when (this) { - Modifier.PUBLIC -> KModifier.PUBLIC - Modifier.PROTECTED -> KModifier.PROTECTED - Modifier.PRIVATE -> KModifier.PRIVATE - Modifier.ABSTRACT -> KModifier.ABSTRACT - FINAL -> KModifier.FINAL - else -> null +/** Gross, but we can't extract values from AnnotationSpecs by member names alone. */ +private fun List?.jsonName(): String? { + if (this == null) return null + return find { it.className == JSON }?.let { + it.members[0].toString().removePrefix("name = \"").removeSuffix("\"") } } -private fun Modifier.asAnnotation(): AnnotationSpec? { - return when (this) { - DEFAULT -> JvmDefault::class.asAnnotationSpec() - STATIC -> JvmStatic::class.asAnnotationSpec() - TRANSIENT -> Transient::class.asAnnotationSpec() - VOLATILE -> Volatile::class.asAnnotationSpec() - SYNCHRONIZED -> Synchronized::class.asAnnotationSpec() - NATIVE -> JvmDefault::class.asAnnotationSpec() - else -> null - } +private fun String.escapeDollarSigns(): String { + return replace("\$", "\${\'\$\'}") } - -private fun KClass.asAnnotationSpec(): AnnotationSpec { - return AnnotationSpec.builder(this).build() -} - -private val Element?.qualifiers: Set - get() { - if (this == null) return setOf() - return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java) - } - -private val Element?.jsonName: String? - get() { - if (this == null) return null - return getAnnotation(Json::class.java)?.name - } diff --git a/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt b/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt index f60200c..cb21606 100644 --- a/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt +++ b/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessorTest.kt @@ -18,6 +18,7 @@ package com.squareup.moshi.kotlin.codegen import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.SourceFile.Companion.kotlin +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview import org.assertj.core.api.Assertions.assertThat import org.junit.Ignore import org.junit.Rule @@ -25,6 +26,7 @@ import org.junit.Test import org.junit.rules.TemporaryFolder /** Execute kotlinc to confirm that either files are generated or errors are printed. */ +@UseExperimental(KotlinPoetMetadataPreview::class) class JsonClassCodegenProcessorTest { @Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder() diff --git a/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/TypeResolverTest.kt b/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/TypeResolverTest.kt deleted file mode 100644 index 8d1c339..0000000 --- a/kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/TypeResolverTest.kt +++ /dev/null @@ -1,47 +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.truth.Truth.assertThat -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter -import com.squareup.kotlinpoet.WildcardTypeName -import com.squareup.kotlinpoet.asClassName -import com.squareup.moshi.kotlin.codegen.api.TypeResolver -import org.junit.Test - -class TypeResolverTest { - private val resolver = TypeResolver() - - @Test - fun ensureClassNameNullabilityIsPreserved() { - assertThat(resolver.resolve(Int::class.asClassName().copy(nullable = true)).isNullable).isTrue() - } - - @Test - fun ensureParameterizedNullabilityIsPreserved() { - val nullableTypeName = List::class.plusParameter(String::class).copy(nullable = true) - - assertThat(resolver.resolve(nullableTypeName).isNullable).isTrue() - } - - @Test - fun ensureWildcardNullabilityIsPreserved() { - val nullableTypeName = WildcardTypeName.producerOf(List::class.asClassName()) - .copy(nullable = true) - - assertThat(resolver.resolve(nullableTypeName).isNullable).isTrue() - } -} diff --git a/pom.xml b/pom.xml index 501a2e9..ba58ee3 100644 --- a/pom.xml +++ b/pom.xml @@ -31,15 +31,13 @@ 1.7 - 0.10 1.0-rc5 0.9.17 0.2 1.16.0 2.1.0 1.3.40 - 1.3.0 - 1.4.0 + 1.4.0 3.1.0 @@ -118,11 +116,6 @@ kotlin-annotation-processing-embeddable ${kotlin.version} - - me.eugeniomarletti.kotlin.metadata - kotlin-metadata - ${kotlin-metadata.version} - com.google.testing.compile compile-testing