diff --git a/build.gradle.kts b/build.gradle.kts index 679b552..bf9f351 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,83 +13,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import com.diffplug.spotless.LineEnding import io.gitlab.arturbosch.detekt.Detekt +import java.net.URL import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.net.URL plugins { - kotlin("jvm") version "1.5.31" - id("org.jetbrains.dokka") version "1.5.31" + kotlin("jvm") version "1.8.21" + id("org.jetbrains.dokka") version "1.8.10" id("com.google.devtools.ksp") version "1.8.21-1.0.11" - id("com.diffplug.spotless") version "6.0.0" + id("com.diffplug.spotless") version "6.18.0" id("com.vanniktech.maven.publish") version "0.18.0" id("io.gitlab.arturbosch.detekt") version "1.18.1" } -repositories { - mavenCentral() -} +repositories { mavenCentral() } pluginManager.withPlugin("java") { - configure { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } - } + configure { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } - project.tasks.withType().configureEach { - options.release.set(8) - } + project.tasks.withType().configureEach { options.release.set(8) } } tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - @Suppress("SuspiciousCollectionReassignment") - freeCompilerArgs += listOf("-progressive", "-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=com.slack.auto.value.kotlin.ExperimentalAvkApi") + compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) + freeCompilerArgs.addAll( + "-progressive", + "-opt-in=com.slack.auto.value.kotlin.ExperimentalAvkApi" + ) } } -tasks.withType().configureEach { - jvmTarget = "1.8" -} +tasks.withType().configureEach { jvmTarget = "1.8" } -kotlin { - explicitApi() -} +kotlin { explicitApi() } tasks.named("dokkaHtml") { outputDirectory.set(rootDir.resolve("docs/0.x")) dokkaSourceSets.configureEach { skipDeprecated.set(true) - externalDocumentationLink { - url.set(URL("https://square.github.io/moshi/1.x/moshi/")) - } + externalDocumentationLink { url.set(URL("https://square.github.io/moshi/1.x/moshi/")) } } } spotless { + lineEndings = LineEnding.PLATFORM_NATIVE format("misc") { target("*.md", ".gitignore") trimTrailingWhitespace() endWithNewline() } - val ktlintVersion = "0.41.0" - val ktlintUserData = mapOf("indent_size" to "2", "continuation_indent_size" to "2") + val ktfmtVersion = "0.43" kotlin { target("**/*.kt") - ktlint(ktlintVersion).userData(ktlintUserData) + ktfmt(ktfmtVersion).googleStyle() trimTrailingWhitespace() endWithNewline() licenseHeaderFile("spotless/spotless.kt") targetExclude("**/spotless.kt") } kotlinGradle { - ktlint(ktlintVersion).userData(ktlintUserData) + ktfmt(ktfmtVersion).googleStyle() trimTrailingWhitespace() endWithNewline() - licenseHeaderFile("spotless/spotless.kt", "(import|plugins|buildscript|dependencies|pluginManagement|rootProject)") + licenseHeaderFile( + "spotless/spotless.kt", + "(import|plugins|buildscript|dependencies|pluginManagement|rootProject)" + ) } } @@ -109,6 +102,7 @@ tasks.withType().configureEach { } val moshiVersion = "1.14.0" + dependencies { ksp("dev.zacsweers.autoservice:auto-service-ksp:1.0.0") implementation("com.squareup.moshi:moshi:$moshiVersion") diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..c1962a7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102..37aef8d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..aeb74cb 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -143,12 +140,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in @@ -205,6 +210,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtension.kt b/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtension.kt index cec5fb8..6ecc182 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtension.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtension.kt @@ -63,7 +63,9 @@ public class AutoValueKotlinExtension( override fun getSupportedOptions(): Set = Options.ALL - override fun incrementalType(processingEnvironment: ProcessingEnvironment): IncrementalExtensionType { + override fun incrementalType( + processingEnvironment: ProcessingEnvironment + ): IncrementalExtensionType { // This is intentionally not incremental, we are generating into source sets directly return IncrementalExtensionType.UNKNOWN } @@ -86,7 +88,10 @@ public class AutoValueKotlinExtension( classToExtend: String, isFinal: Boolean ): String? { - if (options.targets.isNotEmpty() && context.autoValueClass().simpleName.toString() !in options.targets) { + if ( + options.targets.isNotEmpty() && + context.autoValueClass().simpleName.toString() !in options.targets + ) { return null } @@ -94,70 +99,63 @@ public class AutoValueKotlinExtension( val isTopLevel = avClass.nestingKind == NestingKind.TOP_LEVEL if (!isTopLevel) { - val isParentAv = isAnnotationPresent( - MoreElements.asType(avClass.enclosingElement), - AutoValue::class.java - ) + val isParentAv = + isAnnotationPresent(MoreElements.asType(avClass.enclosingElement), AutoValue::class.java) if (!isParentAv) { - val diagnosticKind = if (options.ignoreNested) { - Diagnostic.Kind.WARNING - } else { - Diagnostic.Kind.ERROR - } - realMessager - .printMessage( - diagnosticKind, - "Cannot convert nested classes to Kotlin safely. Please move this to top-level first.", - avClass - ) + val diagnosticKind = + if (options.ignoreNested) { + Diagnostic.Kind.WARNING + } else { + Diagnostic.Kind.ERROR + } + realMessager.printMessage( + diagnosticKind, + "Cannot convert nested classes to Kotlin safely. Please move this to top-level first.", + avClass + ) } } // Check for non-builder nested classes, which cannot be converted with this - val nonBuilderNestedTypes = ElementFilter.typesIn(avClass.enclosedElements) - .filterNot { nestedType -> - context.builder() - .map { nestedType == it.builderType() } - .orElse(false) + val nonBuilderNestedTypes = + ElementFilter.typesIn(avClass.enclosedElements).filterNot { nestedType -> + context.builder().map { nestedType == it.builderType() }.orElse(false) } val (enums, nonEnums) = nonBuilderNestedTypes.partition { it.kind == ElementKind.ENUM } - val (nestedAvClasses, remainingTypes) = nonEnums.partition { isAnnotationPresent(it, AutoValue::class.java) } + val (nestedAvClasses, remainingTypes) = + nonEnums.partition { isAnnotationPresent(it, AutoValue::class.java) } if (remainingTypes.isNotEmpty()) { remainingTypes.forEach { - realMessager - .printMessage( - Diagnostic.Kind.ERROR, - "Cannot convert non-autovalue nested classes to Kotlin safely. Please move this to top-level first.", - it - ) + realMessager.printMessage( + Diagnostic.Kind.ERROR, + "Cannot convert non-autovalue nested classes to Kotlin safely. Please move this to top-level first.", + it + ) } return null } val collectedEnumsLocal = mutableMapOf() for (enumType in enums) { - val (cn, spec) = EnumConversion.convert( - elements, - realMessager, - enumType - ) ?: continue + val (cn, spec) = EnumConversion.convert(elements, realMessager, enumType) ?: continue // If the AV class is Moshi-serializable, treat the enum as such too - val addJsonClass = isAnnotationPresent(avClass, JsonClass::class.java) && - spec.annotationSpecs.none { it.typeName == JSON_CLASS_CN } - collectedEnumsLocal[cn] = if (addJsonClass) { - spec.toBuilder() - .addAnnotation( - AnnotationSpec.builder(JSON_CLASS_CN) - .addMember("generateAdapter = false") - .build() - ) - .build() - } else { - spec - } + val addJsonClass = + isAnnotationPresent(avClass, JsonClass::class.java) && + spec.annotationSpecs.none { it.typeName == JSON_CLASS_CN } + collectedEnumsLocal[cn] = + if (addJsonClass) { + spec + .toBuilder() + .addAnnotation( + AnnotationSpec.builder(JSON_CLASS_CN).addMember("generateAdapter = false").build() + ) + .build() + } else { + spec + } } val classDoc = avClass.parseDocs(elements) @@ -168,30 +166,29 @@ public class AutoValueKotlinExtension( return removeIf { val type = it.typeName if (type !is ClassName) return@removeIf false - (type.simpleName == "Redacted") - .also { wasRedacted -> - if (wasRedacted) { - redactedClassName = type - } + (type.simpleName == "Redacted").also { wasRedacted -> + if (wasRedacted) { + redactedClassName = type } + } } } - val properties = context.properties() - .entries - .associate { (propertyName, method) -> - val annotations = method.annotationMirrors - .map { AnnotationSpec.get(it) } - .filter { spec -> - if (spec.typeName == JSON_CN) { - // Don't include `@Json` if the name value is the same as the property name as it's - // redundant - method.getAnnotation(Json::class.java)!!.name != propertyName - } else { - true + val properties = + context.properties().entries.associate { (propertyName, method) -> + val annotations = + method.annotationMirrors + .map { AnnotationSpec.get(it) } + .filter { spec -> + if (spec.typeName == JSON_CN) { + // Don't include `@Json` if the name value is the same as the property name as it's + // redundant + method.getAnnotation(Json::class.java)!!.name != propertyName + } else { + true + } } - } - .toMutableList() + .toMutableList() val isNullable = annotations.removeIf { (it.typeName as ClassName).simpleName == "Nullable" } annotations.removeIf { @@ -201,22 +198,25 @@ public class AutoValueKotlinExtension( val isAnOverride = annotations.removeIf { (it.typeName as ClassName).simpleName == "Override" } val isRedacted = annotations.anyRedacted() - propertyName to PropertyContext( - name = propertyName, - funName = method.simpleName.toString(), - type = method.returnType.asSafeTypeName().copy(nullable = isNullable), - annotations = annotations, - isOverride = isAnOverride, - isRedacted = isRedacted, - visibility = if (Modifier.PUBLIC in method.modifiers) KModifier.PUBLIC else KModifier.INTERNAL, - doc = method.parseDocs(elements) - ) + propertyName to + PropertyContext( + name = propertyName, + funName = method.simpleName.toString(), + type = method.returnType.asSafeTypeName().copy(nullable = isNullable), + annotations = annotations, + isOverride = isAnOverride, + isRedacted = isRedacted, + visibility = + if (Modifier.PUBLIC in method.modifiers) KModifier.PUBLIC else KModifier.INTERNAL, + doc = method.parseDocs(elements) + ) } val classAnnotations = avClass.classAnnotations().toMutableList() - val isClassRedacted = classAnnotations.anyRedacted() || - // If all the properties are redacted, redacted the class? - properties.values.all { it.isRedacted } + val isClassRedacted = + classAnnotations.anyRedacted() || + // If all the properties are redacted, redacted the class? + properties.values.all { it.isRedacted } val isParcelable = context.processingEnvironment().isParcelable(avClass) @@ -231,11 +231,9 @@ public class AutoValueKotlinExtension( if (context.builder().isPresent) { val builder = context.builder().get() toBuilderMethods += builder.toBuilderMethods() - val propsList = properties.values.map { - CodeBlock.of("%1L·=·%1L", it.name) - } - toBuilderFunSpecs += builder.toBuilderMethods() - .map { + val propsList = properties.values.map { CodeBlock.of("%1L·=·%1L", it.name) } + toBuilderFunSpecs += + builder.toBuilderMethods().map { // Assume it's just one with no params FunSpec.copyOf(it) .withDocsFrom(it) @@ -252,8 +250,8 @@ public class AutoValueKotlinExtension( avkBuilder = AvkBuilder.from(builder, propertyTypes) { parseDocs(elements) } builderFactories += builder.builderMethods() - builderFactorySpecs += builder.builderMethods() - .map { + builderFactorySpecs += + builder.builderMethods().map { FunSpec.copyOf(it) .withDocsFrom(it) .addStatement("TODO(%S)", "Replace this with the implementation from the source class") @@ -261,8 +259,7 @@ public class AutoValueKotlinExtension( } } - val allMethods = ElementFilter.methodsIn(avClass.enclosedElements) - .toMutableSet() + val allMethods = ElementFilter.methodsIn(avClass.enclosedElements).toMutableSet() val staticCreators = mutableListOf() val staticCreatorSpecs = mutableListOf() @@ -272,58 +269,48 @@ public class AutoValueKotlinExtension( .forEach { staticCreator -> allMethods.remove(staticCreator) staticCreators += staticCreator - val spec = FunSpec.copyOf(staticCreator) - .withDocsFrom(staticCreator) - .apply { - if (parameters.size > MAX_PARAMS) { - addAnnotation( - AnnotationSpec.builder(Suppress::class) - .addMember("%S", "LongParameterList") - .build() - ) + val spec = + FunSpec.copyOf(staticCreator) + .withDocsFrom(staticCreator) + .apply { + if (parameters.size > MAX_PARAMS) { + addAnnotation( + AnnotationSpec.builder(Suppress::class) + .addMember("%S", "LongParameterList") + .build() + ) + } } - } - .build() + .build() val isFullCreator = spec.parameters.map { it.type } == properties.values.map { it.type } if (isFullCreator) { // Add deprecated copy of the original, hide from java val parameterString = spec.parameters.joinToString(", ") { it.name } - staticCreatorSpecs += spec.toBuilder() - .addAnnotation( - AnnotationSpec.builder(JvmName::class) - .addMember("%S", "-${spec.name}") - .build() - ) - .addAnnotation( - deprecatedAnnotation( - "Use invoke()", - "${spec.returnType}($parameterString)" + staticCreatorSpecs += + spec + .toBuilder() + .addAnnotation( + AnnotationSpec.builder(JvmName::class).addMember("%S", "-${spec.name}").build() ) - ) - .addStatement("%N(%L)", spec.name, parameterString) - .addStatement("TODO(%S)", "Remove this function. Use the above line to auto-migrate.") - .build() + .addAnnotation( + deprecatedAnnotation("Use invoke()", "${spec.returnType}($parameterString)") + ) + .addStatement("%N(%L)", spec.name, parameterString) + .addStatement("TODO(%S)", "Remove this function. Use the above line to auto-migrate.") + .build() // Expose the original creator for java - val propsList = spec.parameters.map { - CodeBlock.of("%1L·=·%1L", it.name) - } - staticCreatorSpecs += spec.toBuilder(name = "invoke") - .addAnnotation( - AnnotationSpec.builder(JvmName::class) - .addMember("%S", spec.name) - .build() - ) - .addModifiers(KModifier.OPERATOR) - .addStatement( - "return·%T(%L)", - avClass.asClassName(), - propsList.joinToCode(",·") - ) - .build() + val propsList = spec.parameters.map { CodeBlock.of("%1L·=·%1L", it.name) } + staticCreatorSpecs += + spec + .toBuilder(name = "invoke") + .addAnnotation( + AnnotationSpec.builder(JvmName::class).addMember("%S", spec.name).build() + ) + .addModifiers(KModifier.OPERATOR) + .addStatement("return·%T(%L)", avClass.asClassName(), propsList.joinToCode(",·")) + .build() } else { - staticCreatorSpecs += spec.toBuilder() - .addStatement("TODO()") - .build() + staticCreatorSpecs += spec.toBuilder().addStatement("TODO()").build() } } @@ -333,8 +320,7 @@ public class AutoValueKotlinExtension( .filter { it.simpleName.toString().startsWith("with") } .forEach { method -> val name = method.simpleName.toString() - val propertyName = name.removePrefix("with") - .replaceFirstChar { it.lowercase(Locale.US) } + val propertyName = name.removePrefix("with").replaceFirstChar { it.lowercase(Locale.US) } val prop = properties[propertyName] ?: return@forEach // Match return type val isValidReturnType = method.returnType.asSafeTypeName() == avType @@ -346,100 +332,110 @@ public class AutoValueKotlinExtension( val isValidWith = isAutoValueWith || (isValidReturnType && isMatchingSignature) if (isValidWith) { allMethods -= method - witherSpecs += FunSpec.builder(name) - .addParameter(prop.name, prop.type) - .returns(avType) - .apply { - // If we have a builder, use it rather than copy() to pick up any normalization - // or checks it performs in building. - val toBuilderFun = toBuilderFunSpecs.find { it.parameters.isEmpty() } - if (toBuilderFun != null) { - val builderMethodName = context.builder().get() - .setters().getValue(prop.name).first().simpleName.toString() - addStatement( - "return %N().%L(%N).build()", - toBuilderFun, - builderMethodName, - prop.name - ) - } else { - addStatement("return copy(%1N = %1N)", prop.name) + witherSpecs += + FunSpec.builder(name) + .addParameter(prop.name, prop.type) + .returns(avType) + .apply { + // If we have a builder, use it rather than copy() to pick up any normalization + // or checks it performs in building. + val toBuilderFun = toBuilderFunSpecs.find { it.parameters.isEmpty() } + if (toBuilderFun != null) { + val builderMethodName = + context + .builder() + .get() + .setters() + .getValue(prop.name) + .first() + .simpleName + .toString() + addStatement( + "return %N().%L(%N).build()", + toBuilderFun, + builderMethodName, + prop.name + ) + } else { + addStatement("return copy(%1N = %1N)", prop.name) + } } - } - .build() + .build() } } // Get methods not defined in properties - val remainingMethods = allMethods - .asSequence() - .filterNot { it in propertyMethods } - .filterNot { it in toBuilderMethods } - .filterNot { it in staticCreators } - .filterNot { it in builderFactories } - .map { "${it.modifiers.joinToString(" ")} ${it.returnType} ${it.simpleName}(...)" } - .toList() + val remainingMethods = + allMethods + .asSequence() + .filterNot { it in propertyMethods } + .filterNot { it in toBuilderMethods } + .filterNot { it in staticCreators } + .filterNot { it in builderFactories } + .map { "${it.modifiers.joinToString(" ")} ${it.returnType} ${it.simpleName}(...)" } + .toList() // Look for static final fields - val staticConstants = ElementFilter.fieldsIn(avClass.enclosedElements) - .filter { Modifier.STATIC in it.modifiers } - .map { field -> - val type = field.asType().asSafeTypeName() - PropertySpec.builder(field.simpleName.toString(), type) - .apply { - val visibility = when { - Modifier.PRIVATE in field.modifiers -> KModifier.PRIVATE - Modifier.PUBLIC !in field.modifiers -> KModifier.INTERNAL - else -> null - } - visibility?.let { addModifiers(it) } - - field.constantValue?.let { - addModifiers(KModifier.CONST) - if (it is String) { - initializer("%S", it) - } else { - // Best-effort. We could be more precise with this, but it's noisy and annoying - initializer("%L", it) + val staticConstants = + ElementFilter.fieldsIn(avClass.enclosedElements) + .filter { Modifier.STATIC in it.modifiers } + .map { field -> + val type = field.asType().asSafeTypeName() + PropertySpec.builder(field.simpleName.toString(), type) + .apply { + val visibility = + when { + Modifier.PRIVATE in field.modifiers -> KModifier.PRIVATE + Modifier.PUBLIC !in field.modifiers -> KModifier.INTERNAL + else -> null + } + visibility?.let { addModifiers(it) } + + field.constantValue?.let { + addModifiers(KModifier.CONST) + if (it is String) { + initializer("%S", it) + } else { + // Best-effort. We could be more precise with this, but it's noisy and annoying + initializer("%L", it) + } } - } ?: run { - initializer("TODO()") - } + ?: run { initializer("TODO()") } - field.parseDocs(elements)?.let { addKdoc(it) } - } - .build() - } + field.parseDocs(elements)?.let { addKdoc(it) } + } + .build() + } - val superclass = avClass.superclass.asSafeTypeName() - .takeUnless { it == ClassName("java.lang", "Object") } + val superclass = + avClass.superclass.asSafeTypeName().takeUnless { it == ClassName("java.lang", "Object") } collectedEnums += collectedEnumsLocal - val kClass = KotlinClass( - packageName = context.packageName(), - doc = classDoc, - name = avClass.simpleName.toString(), - visibility = avClass.visibility, - isRedacted = isClassRedacted, - isParcelable = isParcelable, - superClass = superclass, - interfaces = avClass.interfaces.associate { it.asSafeTypeName() to null }, - typeParams = avClass.typeParameters.map { it.asTypeVariableName() }, - properties = properties, - avkBuilder = avkBuilder, - toBuilderSpecs = toBuilderFunSpecs, - builderFactories = builderFactorySpecs, - staticCreators = staticCreatorSpecs, - withers = witherSpecs, - remainingMethods = remainingMethods, - classAnnotations = avClass.classAnnotations(), - redactedClassName = redactedClassName, - staticConstants = staticConstants, - isTopLevel = isTopLevel, - children = nestedAvClasses - .mapTo(LinkedHashSet()) { it.asClassName() } - .plus(collectedEnumsLocal.keys) - ) + val kClass = + KotlinClass( + packageName = context.packageName(), + doc = classDoc, + name = avClass.simpleName.toString(), + visibility = avClass.visibility, + isRedacted = isClassRedacted, + isParcelable = isParcelable, + superClass = superclass, + interfaces = avClass.interfaces.associate { it.asSafeTypeName() to null }, + typeParams = avClass.typeParameters.map { it.asTypeVariableName() }, + properties = properties, + avkBuilder = avkBuilder, + toBuilderSpecs = toBuilderFunSpecs, + builderFactories = builderFactorySpecs, + staticCreators = staticCreatorSpecs, + withers = witherSpecs, + remainingMethods = remainingMethods, + classAnnotations = avClass.classAnnotations(), + redactedClassName = redactedClassName, + staticConstants = staticConstants, + isTopLevel = isTopLevel, + children = + nestedAvClasses.mapTo(LinkedHashSet()) { it.asClassName() }.plus(collectedEnumsLocal.keys) + ) collectedKclassees[context.autoValueClass().asClassName()] = kClass @@ -453,22 +449,18 @@ private fun AvkBuilder.Companion.from( parseDocs: Element.() -> String? ): AvkBuilder { // Setters - val props = builderContext.setters().entries.map { (prop, setters) -> - val type = propertyTypes.getValue(prop) - BuilderProperty( - prop, - type, - setters.mapTo(LinkedHashSet()) { - FunSpec.copyOf(it) - .withDocsFrom(it, parseDocs) - .build() - } - ) - } + val props = + builderContext.setters().entries.map { (prop, setters) -> + val type = propertyTypes.getValue(prop) + BuilderProperty( + prop, + type, + setters.mapTo(LinkedHashSet()) { FunSpec.copyOf(it).withDocsFrom(it, parseDocs).build() } + ) + } - val builderMethods = builderContext.setters().values.flatten() - .plus(builderContext.autoBuildMethod()) - .toMutableSet() + val builderMethods = + builderContext.setters().values.flatten().plus(builderContext.autoBuildMethod()).toMutableSet() if (builderContext.buildMethod().isPresent) { builderMethods += builderContext.buildMethod().get() @@ -489,16 +481,15 @@ private fun AvkBuilder.Companion.from( doc = builderContext.builderType().parseDocs(), visibility = builderContext.builderType().visibility, builderProps = props, - buildFun = builderContext.buildMethod() - .map { - FunSpec.copyOf(it) - .withDocsFrom(it, parseDocs) - .build() - } - .orElse(null), - autoBuildFun = FunSpec.copyOf(builderContext.autoBuildMethod()) - .withDocsFrom(builderContext.autoBuildMethod(), parseDocs) - .build(), + buildFun = + builderContext + .buildMethod() + .map { FunSpec.copyOf(it).withDocsFrom(it, parseDocs).build() } + .orElse(null), + autoBuildFun = + FunSpec.copyOf(builderContext.autoBuildMethod()) + .withDocsFrom(builderContext.autoBuildMethod(), parseDocs) + .build(), remainingMethods = remainingMethods, classAnnotations = builderContext.builderType().classAnnotations() ) diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinProcessor.kt b/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinProcessor.kt index 881bf11..0593750 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinProcessor.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinProcessor.kt @@ -21,9 +21,6 @@ import com.google.auto.value.extension.AutoValueExtension import com.google.auto.value.processor.AutoValueProcessor import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeSpec -import okio.Buffer -import okio.blackholeSink -import okio.buffer import java.io.InputStream import java.io.OutputStream import java.io.Reader @@ -54,13 +51,18 @@ import javax.tools.JavaFileManager.Location import javax.tools.JavaFileObject import javax.tools.JavaFileObject.Kind.CLASS import javax.tools.JavaFileObject.Kind.OTHER +import okio.Buffer +import okio.blackholeSink +import okio.buffer @SupportedOptions(Options.OPT_SRC, Options.OPT_IGNORE_NESTED, Options.OPT_TARGETS) @AutoService(Processor::class) public class AutoValueKotlinProcessor : AbstractProcessor() { - private val collectedClasses: MutableMap = ConcurrentHashMap() - private val collectedEnums: MutableMap = ConcurrentHashMap() + private val collectedClasses: MutableMap = + ConcurrentHashMap() + private val collectedEnums: MutableMap = + ConcurrentHashMap() private lateinit var options: Options override fun init(processingEnv: ProcessingEnvironment) { @@ -77,10 +79,7 @@ public class AutoValueKotlinProcessor : AbstractProcessor() { } @Suppress("ReturnCount") - override fun process( - annotations: Set, - roundEnv: RoundEnvironment - ): Boolean { + override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { val avClasses = roundEnv.getElementsAnnotatedWith(AutoValue::class.java) if (avClasses.isNotEmpty()) { processAvElements(annotations, roundEnv, avClasses) @@ -92,10 +91,8 @@ public class AutoValueKotlinProcessor : AbstractProcessor() { processingEnv.messager.printMessage(ERROR, "No AutoValue classes found") return false } - val srcDir = - processingEnv.options[Options.OPT_SRC] ?: error("Missing src dir option") - val roots = collectedClasses.filterValues { it.isTopLevel } - .toMutableMap() + val srcDir = processingEnv.options[Options.OPT_SRC] ?: error("Missing src dir option") + val roots = collectedClasses.filterValues { it.isTopLevel }.toMutableMap() for ((_, root) in roots) { val spec = composeTypeSpec(root) spec.writeCleanlyTo(root.packageName, srcDir) @@ -106,7 +103,8 @@ public class AutoValueKotlinProcessor : AbstractProcessor() { private fun composeTypeSpec(kotlinClass: KotlinClass): TypeSpec { val spec = kotlinClass.toTypeSpec(processingEnv.messager) - return spec.toBuilder() + return spec + .toBuilder() .apply { for (child in kotlinClass.children) { val enumChild = collectedEnums.remove(child) @@ -114,8 +112,9 @@ public class AutoValueKotlinProcessor : AbstractProcessor() { addType(enumChild) continue } - val childKotlinClass = collectedClasses.remove(child) - ?: error("Missing child class $child for parent ${kotlinClass.name}") + val childKotlinClass = + collectedClasses.remove(child) + ?: error("Missing child class $child for parent ${kotlinClass.name}") addType(composeTypeSpec(childKotlinClass)) } } @@ -127,29 +126,38 @@ public class AutoValueKotlinProcessor : AbstractProcessor() { roundEnv: RoundEnvironment, avClasses: Set ) { - if (options.targets.isNotEmpty() && avClasses.none { it.simpleName.toString() in options.targets }) { + if ( + options.targets.isNotEmpty() && avClasses.none { it.simpleName.toString() in options.targets } + ) { // Nothing to do this round return } // Load extensions ourselves @Suppress("TooGenericExceptionCaught", "SwallowedException") - val extensions = try { - ServiceLoader.load(AutoValueExtension::class.java, AutoValueExtension::class.java.classLoader).toList() - } catch (e: Exception) { - emptyList() - } + val extensions = + try { + ServiceLoader.load( + AutoValueExtension::class.java, + AutoValueExtension::class.java.classLoader + ) + .toList() + } catch (e: Exception) { + emptyList() + } // Make our extension val avkExtension = AutoValueKotlinExtension(processingEnv.messager, options) // Create an in-memory av processor and run it val avProcessor = AutoValueProcessor(extensions + avkExtension) - avProcessor.init(object : ProcessingEnvironment by processingEnv { - override fun getMessager(): Messager = NoOpMessager + avProcessor.init( + object : ProcessingEnvironment by processingEnv { + override fun getMessager(): Messager = NoOpMessager - override fun getFiler(): Filer = NoOpFiler - }) + override fun getFiler(): Filer = NoOpFiler + } + ) avProcessor.process(annotations, roundEnv) // Save off our extracted classes @@ -163,11 +171,7 @@ private object NoOpMessager : Messager { // Do nothing } - override fun printMessage( - kind: Kind, - msg: CharSequence, - element: Element? - ) { + override fun printMessage(kind: Kind, msg: CharSequence, element: Element?) { // Do nothing } diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/AvkBuilder.kt b/src/main/kotlin/com/slack/auto/value/kotlin/AvkBuilder.kt index 25e24d5..88c08ea 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/AvkBuilder.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/AvkBuilder.kt @@ -44,19 +44,15 @@ public data class AvkBuilder( @Suppress("LongMethod") public fun createType(messager: Messager): TypeSpec { - val builder = TypeSpec.classBuilder(name) - .addModifiers(visibility) - .addAnnotations(classAnnotations) + val builder = + TypeSpec.classBuilder(name).addModifiers(visibility).addAnnotations(classAnnotations) - val constructorBuilder = FunSpec.constructorBuilder() - .addModifiers(INTERNAL) + val constructorBuilder = FunSpec.constructorBuilder().addModifiers(INTERNAL) @Suppress("MagicNumber") if (builderProps.size >= MAX_PARAMS) { builder.addAnnotation( - AnnotationSpec.builder(Suppress::class) - .addMember("%S", "LongParameterList") - .build() + AnnotationSpec.builder(Suppress::class).addMember("%S", "LongParameterList").build() ) } @@ -64,44 +60,47 @@ public data class AvkBuilder( for ((propName, type, setters) in builderProps) { // Add param to constructor - val defaultValue = if (type.isNullable) { - CodeBlock.of("null") - } else { - type.defaultPrimitiveValue() - } + val defaultValue = + if (type.isNullable) { + CodeBlock.of("null") + } else { + type.defaultPrimitiveValue() + } val useNullablePropType = defaultValue.toString() == "null" - val param = ParameterSpec.builder(propName, type.copy(nullable = useNullablePropType)) - .defaultValue(defaultValue) - .build() + val param = + ParameterSpec.builder(propName, type.copy(nullable = useNullablePropType)) + .defaultValue(defaultValue) + .build() constructorBuilder.addParameter(param) // Add private properties - val propSpec = PropertySpec.builder(propName, param.type) - .addModifiers(PRIVATE) - .mutable() - .initializer("%N", param.name) - .build() + val propSpec = + PropertySpec.builder(propName, param.type) + .addModifiers(PRIVATE) + .mutable() + .initializer("%N", param.name) + .build() builder.addProperty(propSpec) // Add build() assignment block - val extraCheck = if (type.isNullable || !useNullablePropType) { - CodeBlock.of("") - } else { - CodeBlock.of("·?:·error(%S)", "${propSpec.name} == null") - } + val extraCheck = + if (type.isNullable || !useNullablePropType) { + CodeBlock.of("") + } else { + CodeBlock.of("·?:·error(%S)", "${propSpec.name} == null") + } propsToCreateWith += CodeBlock.of("%1N·=·%1N%2L", propSpec, extraCheck) for (setter in setters) { if (setter.parameters.size != 1) { - messager.printMessage( - WARNING, - "Setter with surprising params: ${setter.name}" - ) + messager.printMessage(WARNING, "Setter with surprising params: ${setter.name}") } - val setterSpec = setter.toBuilder() - // Assume this is a normal setter - .addStatement("return·apply·{·this.%N·= %N }", propSpec, setter.parameters[0]) - .build() + val setterSpec = + setter + .toBuilder() + // Assume this is a normal setter + .addStatement("return·apply·{·this.%N·= %N }", propSpec, setter.parameters[0]) + .build() builder.addFunction(setterSpec) } } @@ -112,7 +111,8 @@ public data class AvkBuilder( // AutoBuild builder.addFunction( - autoBuildFun.toBuilder() + autoBuildFun + .toBuilder() .addStatement( "return·%T(%L)", autoBuildFun.returnType!!, @@ -125,7 +125,9 @@ public data class AvkBuilder( builder.addFunction( FunSpec.builder("placeholder") .apply { - addComment("TODO This is a placeholder to mention the following methods need to be moved manually over:") + addComment( + "TODO This is a placeholder to mention the following methods need to be moved manually over:" + ) for (remaining in remainingMethods) { addComment(" $remaining") } @@ -140,7 +142,11 @@ public data class AvkBuilder( } @ExperimentalAvkApi - public data class BuilderProperty(val name: String, val type: TypeName, val setters: Set) + public data class BuilderProperty( + val name: String, + val type: TypeName, + val setters: Set + ) // Public for extension public companion object diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/EnumConversion.kt b/src/main/kotlin/com/slack/auto/value/kotlin/EnumConversion.kt index 335f6df..8b6b501 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/EnumConversion.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/EnumConversion.kt @@ -45,60 +45,57 @@ public object EnumConversion { ): Pair? { val className = element.asClassName() val docs = element.parseDocs(elements) - return className to TypeSpec.enumBuilder(className.simpleName) - .addAnnotations(element.classAnnotations()) - .addModifiers(element.visibility) - .apply { - docs?.let { - addKdoc(it) - } - var isMoshiSerialized = false - for (field in ElementFilter.fieldsIn(element.enclosedElements)) { - if (field.kind == ENUM_CONSTANT) { - val annotations = field.annotationMirrors - .map { - if (it.annotationType.asTypeName() == JSON_CN) { - isMoshiSerialized = true - } - AnnotationSpec.get(it) - } - addEnumConstant( - field.simpleName.toString(), - TypeSpec.anonymousClassBuilder() - .addAnnotations(annotations) - .apply { - field.parseDocs(elements)?.let { - addKdoc(it) + return className to + TypeSpec.enumBuilder(className.simpleName) + .addAnnotations(element.classAnnotations()) + .addModifiers(element.visibility) + .apply { + docs?.let { addKdoc(it) } + var isMoshiSerialized = false + for (field in ElementFilter.fieldsIn(element.enclosedElements)) { + if (field.kind == ENUM_CONSTANT) { + val annotations = + field.annotationMirrors.map { + if (it.annotationType.asTypeName() == JSON_CN) { + isMoshiSerialized = true } + AnnotationSpec.get(it) } - .build() + addEnumConstant( + field.simpleName.toString(), + TypeSpec.anonymousClassBuilder() + .addAnnotations(annotations) + .apply { field.parseDocs(elements)?.let { addKdoc(it) } } + .build() + ) + } + } + + if (isMoshiSerialized && annotationSpecs.none { it.typeName == JSON_CLASS_CN }) { + addAnnotation( + AnnotationSpec.builder(JSON_CLASS_CN).addMember("generateAdapter = false").build() ) } - } - if (isMoshiSerialized && annotationSpecs.none { it.typeName == JSON_CLASS_CN }) { - addAnnotation( - AnnotationSpec.builder(JSON_CLASS_CN) - .addMember("generateAdapter = false") - .build() - ) - } + for (nestedType in ElementFilter.typesIn(element.enclosedElements)) { + if (nestedType.kind == ENUM) { + convert(elements, messager, nestedType)?.second?.let(::addType) + } else { + messager.printMessage( + ERROR, + "Nested types in enums can only be other enums", + nestedType + ) + return null + } + } - for (nestedType in ElementFilter.typesIn(element.enclosedElements)) { - if (nestedType.kind == ENUM) { - convert(elements, messager, nestedType)?.second?.let(::addType) - } else { - messager.printMessage(ERROR, "Nested types in enums can only be other enums", nestedType) + for (method in ElementFilter.methodsIn(element.enclosedElements)) { + if (method.simpleName.toString() in setOf("values", "valueOf")) continue + messager.printMessage(ERROR, "Cannot convert nested enums with methods", method) return null } } - - for (method in ElementFilter.methodsIn(element.enclosedElements)) { - if (method.simpleName.toString() in setOf("values", "valueOf")) continue - messager.printMessage(ERROR, "Cannot convert nested enums with methods", method) - return null - } - } - .build() + .build() } } diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/KotlinClass.kt b/src/main/kotlin/com/slack/auto/value/kotlin/KotlinClass.kt index 2722d3c..926c765 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/KotlinClass.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/KotlinClass.kt @@ -58,10 +58,11 @@ public data class KotlinClass( ) { @Suppress("LongMethod", "ComplexMethod") public fun toTypeSpec(messager: Messager): TypeSpec { - val typeBuilder = TypeSpec.classBuilder(name) - .addModifiers(DATA) - .addAnnotations(classAnnotations) - .addTypeVariables(typeParams) + val typeBuilder = + TypeSpec.classBuilder(name) + .addModifiers(DATA) + .addAnnotations(classAnnotations) + .addTypeVariables(typeParams) if (doc != null) { typeBuilder.addKdoc(doc) @@ -109,16 +110,15 @@ public data class KotlinClass( .build() ) - val defaultCodeBlock = when { - // Builders handle the defaults unless it's a Json class, in which case we need both! - avkBuilder != null && !needsConstructorDefaultValues -> null - else -> prop.defaultValue - } + val defaultCodeBlock = + when { + // Builders handle the defaults unless it's a Json class, in which case we need both! + avkBuilder != null && !needsConstructorDefaultValues -> null + else -> prop.defaultValue + } constructorBuilder.addParameter( ParameterSpec.builder(prop.name, prop.type) - .apply { - defaultCodeBlock?.let { defaultValue(it) } - } + .apply { defaultCodeBlock?.let { defaultValue(it) } } .build() ) @@ -131,11 +131,7 @@ public data class KotlinClass( .apply { // Prop uses getter syntax, kotlin usages are fine if (prop.usesGet) { - addAnnotation( - AnnotationSpec.builder(JvmName::class) - .addMember("%S", "-") - .build() - ) + addAnnotation(AnnotationSpec.builder(JvmName::class).addMember("%S", "-").build()) } if (prop.isOverride) { // It's from an interface/base class, so leave this as is @@ -145,13 +141,14 @@ public data class KotlinClass( // Hide from Java, deprecate for Kotlin users addAnnotation(JvmSynthetic::class) addAnnotation( - AnnotationSpec.builder(JvmName::class) - .addMember("%S", "-${prop.name}") - .build() + AnnotationSpec.builder(JvmName::class).addMember("%S", "-${prop.name}").build() ) addAnnotation(deprecatedAnnotation("Use the property", prop.name)) addStatement("%N()", prop.funName) - addStatement("TODO(%S)", "Remove this function. Use the above line to auto-migrate.") + addStatement( + "TODO(%S)", + "Remove this function. Use the above line to auto-migrate." + ) } } .returns(prop.type) @@ -171,8 +168,7 @@ public data class KotlinClass( } } - val companionObjectBuilder = TypeSpec.companionObjectBuilder() - .addProperties(staticConstants) + val companionObjectBuilder = TypeSpec.companionObjectBuilder().addProperties(staticConstants) if (staticCreators.isNotEmpty()) { companionObjectBuilder.addFunctions(staticCreators) @@ -187,7 +183,9 @@ public data class KotlinClass( typeBuilder.addFunction( FunSpec.builder("placeholder") .apply { - addComment("TODO This is a placeholder to mention the following methods need to be moved manually over:") + addComment( + "TODO This is a placeholder to mention the following methods need to be moved manually over:" + ) for (remaining in remainingMethods) { addComment(" $remaining") } @@ -206,8 +204,9 @@ public data class KotlinClass( typeBuilder.addType(avkBuilder.createType(messager)) } - val shouldIncludeCompanion = companionObjectBuilder.funSpecs.isNotEmpty() || - companionObjectBuilder.propertySpecs.isNotEmpty() + val shouldIncludeCompanion = + companionObjectBuilder.funSpecs.isNotEmpty() || + companionObjectBuilder.propertySpecs.isNotEmpty() if (shouldIncludeCompanion) { typeBuilder.addType(companionObjectBuilder.build()) } diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/Options.kt b/src/main/kotlin/com/slack/auto/value/kotlin/Options.kt index 75db0cc..8239e2d 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/Options.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/Options.kt @@ -21,9 +21,8 @@ public class Options(optionsMap: Map) { public val srcDir: File = optionsMap[OPT_SRC]?.let { File(it) } ?: error("Missing src dir option") - public val targets: Set = optionsMap[OPT_TARGETS]?.splitToSequence(":") - ?.toSet() - ?: emptySet() + public val targets: Set = + optionsMap[OPT_TARGETS]?.splitToSequence(":")?.toSet() ?: emptySet() public val ignoreNested: Boolean = optionsMap[OPT_IGNORE_NESTED]?.toBooleanStrict() ?: false diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/PropertyContext.kt b/src/main/kotlin/com/slack/auto/value/kotlin/PropertyContext.kt index 32e1700..eb3e364 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/PropertyContext.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/PropertyContext.kt @@ -32,10 +32,11 @@ public data class PropertyContext( val isRedacted: Boolean, val visibility: KModifier, val doc: String?, - val defaultValue: CodeBlock? = when { - type.isNullable -> CodeBlock.of("null") - else -> type.defaultPrimitiveValue().takeIf { it.toString() != "null" } - } + val defaultValue: CodeBlock? = + when { + type.isNullable -> CodeBlock.of("null") + else -> type.defaultPrimitiveValue().takeIf { it.toString() != "null" } + } ) { public val usesGet: Boolean = name.startsWith("get") || funName.startsWith("get") diff --git a/src/main/kotlin/com/slack/auto/value/kotlin/utils.kt b/src/main/kotlin/com/slack/auto/value/kotlin/utils.kt index 56cdd23..65ed902 100644 --- a/src/main/kotlin/com/slack/auto/value/kotlin/utils.kt +++ b/src/main/kotlin/com/slack/auto/value/kotlin/utils.kt @@ -15,6 +15,7 @@ */ @file:Suppress("TooManyFunctions") @file:OptIn(DelicateKotlinPoetApi::class) + package com.slack.auto.value.kotlin import com.google.auto.common.Visibility @@ -70,30 +71,27 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.readText import kotlin.io.path.writeText -internal val NONNULL_ANNOTATIONS = setOf( - "NonNull", - "NotNull", - "Nonnull" -) +internal val NONNULL_ANNOTATIONS = setOf("NonNull", "NotNull", "Nonnull") internal val PARCELIZE = ClassName("kotlinx.parcelize", "Parcelize") -internal val INTRINSIC_IMPORTS = setOf( - "import java.lang.String", - "import java.lang.CharSequence", - "import java.lang.Boolean", - "import java.lang.Byte", - "import java.lang.Short", - "import java.lang.Char", - "import java.lang.Int", - "import java.lang.Float", - "import java.lang.Double", - "import java.lang.Object", - "import java.util.Map", - "import java.util.List", - "import java.util.Set", - "import java.util.Collection" -) +internal val INTRINSIC_IMPORTS = + setOf( + "import java.lang.String", + "import java.lang.CharSequence", + "import java.lang.Boolean", + "import java.lang.Byte", + "import java.lang.Short", + "import java.lang.Char", + "import java.lang.Int", + "import java.lang.Float", + "import java.lang.Double", + "import java.lang.Object", + "import java.util.Map", + "import java.util.List", + "import java.util.Set", + "import java.util.Collection" + ) /** Regex used for finding references in javadoc links */ internal const val DOC_LINK_REGEX = "[0-9A-Za-z._]*" @@ -137,20 +135,13 @@ public fun TypeName.normalize(): TypeName { @ExperimentalAvkApi public fun TypeElement.classAnnotations(): List { return annotationMirrors - .map { - @Suppress("DEPRECATION") - AnnotationSpec.get(it) - } + .map { @Suppress("DEPRECATION") AnnotationSpec.get(it) } .filterNot { (it.typeName as ClassName).packageName == "com.google.auto.value" } .filterNot { (it.typeName as ClassName).simpleName == "Metadata" } .map { spec -> if ((spec.typeName as ClassName).simpleName == "JsonClass") { // Strip the 'generator = "avm"' off of this if present - spec.toBuilder() - .apply { - members.removeIf { "avm" in it.toString() } - } - .build() + spec.toBuilder().apply { members.removeIf { "avm" in it.toString() } }.build() } else { spec } @@ -168,7 +159,9 @@ public fun TypeName.defaultPrimitiveValue(): CodeBlock = FLOAT -> CodeBlock.of("0f") LONG -> CodeBlock.of("0L") DOUBLE -> CodeBlock.of("0.0") - UNIT, Void::class.asTypeName(), NOTHING -> { + UNIT, + Void::class.asTypeName(), + NOTHING -> { throw IllegalStateException("Parameter with void, Unit, or Nothing type is illegal") } else -> CodeBlock.of("null") @@ -188,8 +181,7 @@ public fun FunSpec.Companion.copyOf(method: ExecutableElement): FunSpec.Builder var modifiers: Set = method.modifiers val methodName = method.simpleName.toString() - val funBuilder = builder(methodName) - .addModifiers(method.visibility) + val funBuilder = builder(methodName).addModifiers(method.visibility) modifiers = modifiers.toMutableSet() modifiers.remove(Modifier.ABSTRACT) @@ -203,10 +195,8 @@ public fun FunSpec.Companion.copyOf(method: ExecutableElement): FunSpec.Builder funBuilder.returns(method.returnType.asSafeTypeName()) funBuilder.addParameters(ParameterSpec.parametersWithNullabilityOf(method)) if (method.isVarArgs) { - funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last() - .toBuilder() - .addModifiers(KModifier.VARARG) - .build() + funBuilder.parameters[funBuilder.parameters.lastIndex] = + funBuilder.parameters.last().toBuilder().addModifiers(KModifier.VARARG).build() } if (method.thrownTypes.isNotEmpty()) { @@ -222,18 +212,20 @@ public fun FunSpec.Companion.copyOf(method: ExecutableElement): FunSpec.Builder } @ExperimentalAvkApi -public fun ParameterSpec.Companion.parametersWithNullabilityOf(method: ExecutableElement): List = - method.parameters.map(ParameterSpec.Companion::getWithNullability) +public fun ParameterSpec.Companion.parametersWithNullabilityOf( + method: ExecutableElement +): List = method.parameters.map(ParameterSpec.Companion::getWithNullability) @Suppress("DEPRECATION") @ExperimentalAvkApi public fun ParameterSpec.Companion.getWithNullability(element: VariableElement): ParameterSpec { val name = element.simpleName.toString() val isNullable = - element.annotationMirrors.any { (it.annotationType.asElement() as TypeElement).simpleName.toString() == "Nullable" } + element.annotationMirrors.any { + (it.annotationType.asElement() as TypeElement).simpleName.toString() == "Nullable" + } val type = element.asType().asSafeTypeName().copy(nullable = isNullable) - return builder(name, type) - .build() + return builder(name, type).build() } /** Cleans up the generated doc and translates some html to equivalent markdown for Kotlin docs. */ @@ -241,7 +233,8 @@ public fun ParameterSpec.Companion.getWithNullability(element: VariableElement): public fun cleanUpDoc(doc: String): String { // TODO not covered yet // {@link TimeFormatter#getDateTimeString(SlackDateTime)} - return doc.replace("", "*") + return doc + .replace("", "*") .replace("", "*") .replace("

", "") // JavaParser adds a couple spaces to the beginning of these for some reason @@ -262,12 +255,14 @@ public fun cleanUpDoc(doc: String): String { "[$foo.$bar]" } // {@linkplain Foo baz} -> [baz][Foo] - .replace("\\{@linkplain ($DOC_LINK_REGEX) ($DOC_LINK_REGEX)}".toRegex()) { result: MatchResult -> + .replace("\\{@linkplain ($DOC_LINK_REGEX) ($DOC_LINK_REGEX)}".toRegex()) { result: MatchResult + -> val (foo, baz) = result.destructured "[$baz][$foo]" } // {@linkplain Foo#bar baz} -> [baz][Foo.bar] - .replace("\\{@linkplain ($DOC_LINK_REGEX)#($DOC_LINK_REGEX) ($DOC_LINK_REGEX)}".toRegex()) { result: MatchResult -> + .replace("\\{@linkplain ($DOC_LINK_REGEX)#($DOC_LINK_REGEX) ($DOC_LINK_REGEX)}".toRegex()) { + result: MatchResult -> val (foo, bar, baz) = result.destructured "[$baz][$foo.$bar]" } @@ -289,12 +284,10 @@ public fun FunSpec.Builder.withDocsFrom( @ExperimentalAvkApi public fun ProcessingEnvironment.isParcelable(element: TypeElement): Boolean { - return elementUtils - .getTypeElement("android.os.Parcelable") - ?.asType() - ?.let { parcelableClass -> - element.interfaces.any { it.isClassOfType(typeUtils, parcelableClass) } - } ?: false + return elementUtils.getTypeElement("android.os.Parcelable")?.asType()?.let { parcelableClass -> + element.interfaces.any { it.isClassOfType(typeUtils, parcelableClass) } + } + ?: false } private fun TypeMirror.isClassOfType(types: Types, other: TypeMirror?) = @@ -302,19 +295,19 @@ private fun TypeMirror.isClassOfType(types: Types, other: TypeMirror?) = @ExperimentalAvkApi public val Element.visibility: KModifier - get() = when (Visibility.effectiveVisibilityOfElement(this)!!) { - PRIVATE -> KModifier.PRIVATE - DEFAULT -> KModifier.INTERNAL - PROTECTED -> KModifier.PROTECTED - PUBLIC -> KModifier.PUBLIC - } + get() = + when (Visibility.effectiveVisibilityOfElement(this)!!) { + PRIVATE -> KModifier.PRIVATE + DEFAULT -> KModifier.INTERNAL + PROTECTED -> KModifier.PROTECTED + PUBLIC -> KModifier.PUBLIC + } @ExperimentalAvkApi @OptIn(ExperimentalPathApi::class) public fun TypeSpec.writeCleanlyTo(packageName: String, dir: String) { val file = File(dir).toPath() - val outputPath = FileSpec.get(packageName, this) - .writeToLocal(file) + val outputPath = FileSpec.get(packageName, this).writeToLocal(file) val text = outputPath.readText() // Post-process to remove any kotlin intrinsic types // Is this wildly inefficient? yes. Does it really matter in our cases? nah @@ -349,12 +342,14 @@ public fun TypeSpec.writeCleanlyTo(packageName: String, dir: String) { /** Best-effort checks if the string is an import from `kotlin.*` */ @Suppress("MagicNumber") -private val String.isKotlinPackageImport: Boolean get() = startsWith("import kotlin.") && - // Looks like a class - // 14 is the length of `import kotlin.` - get(14).isUpperCase() && - // Exclude if it's importing a nested element - '.' !in removePrefix("import kotlin.") +private val String.isKotlinPackageImport: Boolean + get() = + startsWith("import kotlin.") && + // Looks like a class + // 14 is the length of `import kotlin.` + get(14).isUpperCase() && + // Exclude if it's importing a nested element + '.' !in removePrefix("import kotlin.") private fun FileSpec.writeToLocal(directory: Path): Path { require(Files.notExists(directory) || Files.isDirectory(directory)) { @@ -370,10 +365,9 @@ private fun FileSpec.writeToLocal(directory: Path): Path { Files.createDirectories(srcDirectory) val outputPath = srcDirectory.resolve("$name.kt") - OutputStreamWriter( - Files.newOutputStream(outputPath), - StandardCharsets.UTF_8 - ).use { writer -> writeTo(writer) } + OutputStreamWriter(Files.newOutputStream(outputPath), StandardCharsets.UTF_8).use { writer -> + writeTo(writer) + } return outputPath } diff --git a/src/test/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtensionTest.kt b/src/test/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtensionTest.kt index 0216244..fe70ed9 100644 --- a/src/test/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtensionTest.kt +++ b/src/test/kotlin/com/slack/auto/value/kotlin/AutoValueKotlinExtensionTest.kt @@ -22,20 +22,18 @@ import com.google.testing.compile.CompilationSubject.compilations import com.google.testing.compile.Compiler import com.google.testing.compile.Compiler.javac import com.google.testing.compile.JavaFileObjects.forSourceString +import java.io.File +import javax.tools.JavaFileObject import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.io.File -import javax.tools.JavaFileObject @Suppress("LongMethod", "LargeClass", "MaxLineLength") class AutoValueKotlinExtensionTest { @JvmField @Rule - val tmpFolder: TemporaryFolder = TemporaryFolder.builder() - .assureDeletion() - .build() + val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build() private lateinit var srcDir: File @@ -46,10 +44,11 @@ class AutoValueKotlinExtensionTest { @Test fun smokeTest() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import android.os.Parcelable; @@ -126,15 +125,16 @@ class AutoValueKotlinExtensionTest { abstract Example build(); } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") assertThat(generated.exists()).isTrue() assertThat(generated.readText()) - //language=Kotlin + // language=Kotlin .isEqualTo( """ package test @@ -404,16 +404,18 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } @Test fun creators() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -427,9 +429,10 @@ class AutoValueKotlinExtensionTest { return null; } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -476,16 +479,18 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } @Test fun wither() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -497,9 +502,10 @@ class AutoValueKotlinExtensionTest { abstract String withValue(); } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -543,16 +549,18 @@ class AutoValueKotlinExtensionTest { fun withValue(`value`: String): Example = copy(`value` = `value`) } - """.trimIndent() + """ + .trimIndent() ) } @Test fun defaultsInConstructor() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -600,9 +608,10 @@ class AutoValueKotlinExtensionTest { abstract Double aNullableDoubleReference(); } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -982,16 +991,18 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } @Test fun nestedClasses() { - val result = compile( - forSourceString( - "test.Outer", - """ + val result = + compile( + forSourceString( + "test.Outer", + """ package test; import com.google.auto.value.AutoValue; @@ -1009,9 +1020,10 @@ class AutoValueKotlinExtensionTest { abstract String withValue(); } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Outer.kt") @@ -1071,16 +1083,18 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } @Test fun nestedError_outerNonAuto() { - val result = compile( - forSourceString( - "test.Outer", - """ + val result = + compile( + forSourceString( + "test.Outer", + """ package test; import com.google.auto.value.AutoValue; @@ -1095,20 +1109,24 @@ class AutoValueKotlinExtensionTest { abstract String withValue(); } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.failed() - result.hadErrorContaining("Cannot convert nested classes to Kotlin safely. Please move this to top-level first.") + result.hadErrorContaining( + "Cannot convert nested classes to Kotlin safely. Please move this to top-level first." + ) } @Test fun nestedError_innerNonAuto() { - val result = compile( - forSourceString( - "test.Outer", - """ + val result = + compile( + forSourceString( + "test.Outer", + """ package test; import com.google.auto.value.AutoValue; @@ -1124,20 +1142,24 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.failed() - result.hadErrorContaining("Cannot convert non-autovalue nested classes to Kotlin safely. Please move this to top-level first.") + result.hadErrorContaining( + "Cannot convert non-autovalue nested classes to Kotlin safely. Please move this to top-level first." + ) } @Test fun nestedWarning() { - val result = compile( - forSourceString( - "test.Outer", - """ + val result = + compile( + forSourceString( + "test.Outer", + """ package test; import com.google.auto.value.AutoValue; @@ -1152,22 +1174,26 @@ class AutoValueKotlinExtensionTest { abstract String withValue(); } } - """.trimIndent() - ) - ) { - withOptions(listOf(compilerSrcOption(), "-A${Options.OPT_IGNORE_NESTED}=true")) - } + """ + .trimIndent() + ) + ) { + withOptions(listOf(compilerSrcOption(), "-A${Options.OPT_IGNORE_NESTED}=true")) + } result.succeeded() - result.hadWarningContaining("Cannot convert nested classes to Kotlin safely. Please move this to top-level first.") + result.hadWarningContaining( + "Cannot convert nested classes to Kotlin safely. Please move this to top-level first." + ) } @Test fun targets() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -1176,11 +1202,12 @@ class AutoValueKotlinExtensionTest { abstract class Example { abstract String value(); } - """.trimIndent() - ), - forSourceString( - "test.IgnoredExample", """ + .trimIndent() + ), + forSourceString( + "test.IgnoredExample", + """ package test; import com.google.auto.value.AutoValue; @@ -1189,11 +1216,12 @@ class AutoValueKotlinExtensionTest { abstract class IgnoredExample { abstract String value(); } - """.trimIndent() - ) - ) { - withOptions(listOf(compilerSrcOption(), "-A${Options.OPT_TARGETS}=Example")) - } + """ + .trimIndent() + ) + ) { + withOptions(listOf(compilerSrcOption(), "-A${Options.OPT_TARGETS}=Example")) + } result.succeeded() assertThat(File(srcDir, "test/Example.kt").exists()).isTrue() @@ -1202,10 +1230,11 @@ class AutoValueKotlinExtensionTest { @Test fun getters() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -1214,9 +1243,10 @@ class AutoValueKotlinExtensionTest { abstract class Example { abstract String getValue(); } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -1230,17 +1260,19 @@ class AutoValueKotlinExtensionTest { val `value`: String ) - """.trimIndent() + """ + .trimIndent() ) } // Regression test for https://github.com/slackhq/auto-value-kotlin/issues/16 @Test fun nonJsonClassesDoNotGetDefaults() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -1257,9 +1289,10 @@ class AutoValueKotlinExtensionTest { Example build(); } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -1296,17 +1329,19 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } // Regression test for https://github.com/slackhq/auto-value-kotlin/issues/16 @Test fun jsonClassesWithBuildersGetDefaults() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -1325,9 +1360,10 @@ class AutoValueKotlinExtensionTest { Example build(); } } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -1366,7 +1402,8 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } @@ -1375,10 +1412,11 @@ class AutoValueKotlinExtensionTest { // still kick in? @Test fun gettersMixed() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -1388,9 +1426,10 @@ class AutoValueKotlinExtensionTest { abstract String getValue(); abstract String nonGetValue(); } - """.trimIndent() + """ + .trimIndent() + ) ) - ) result.succeeded() val generated = File(srcDir, "test/Example.kt") @@ -1420,17 +1459,19 @@ class AutoValueKotlinExtensionTest { } } - """.trimIndent() + """ + .trimIndent() ) } // Regression test for https://github.com/slackhq/auto-value-kotlin/issues/15 @Test fun enumsAreNotShared() { - val result = compile( - forSourceString( - "test.Example", - """ + val result = + compile( + forSourceString( + "test.Example", + """ package test; import com.google.auto.value.AutoValue; @@ -1444,11 +1485,12 @@ class AutoValueKotlinExtensionTest { INSTANCE } } - """.trimIndent() - ), - forSourceString( - "test.Example2", """ + .trimIndent() + ), + forSourceString( + "test.Example2", + """ package test; import com.google.auto.value.AutoValue; @@ -1462,9 +1504,10 @@ class AutoValueKotlinExtensionTest { INSTANCE } } - """.trimIndent() - ), - ) + """ + .trimIndent() + ), + ) result.succeeded() } @@ -1477,12 +1520,12 @@ class AutoValueKotlinExtensionTest { vararg sourceFiles: JavaFileObject, compilerBody: Compiler.() -> Compiler = { this } ): CompilationSubject { - val compilation = javac() - .withOptions(compilerSrcOption()) - .withProcessors(AutoValueKotlinProcessor()) - .let(compilerBody) - .compile(*sourceFiles) - return assertAbout(compilations()) - .that(compilation) + val compilation = + javac() + .withOptions(compilerSrcOption()) + .withProcessors(AutoValueKotlinProcessor()) + .let(compilerBody) + .compile(*sourceFiles) + return assertAbout(compilations()).that(compilation) } }