diff --git a/core/api/core.api b/core/api/core.api index 92b7d0481..3e2688124 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -64,6 +64,9 @@ public final class dev/fritz2/core/LensKt { public abstract interface annotation class dev/fritz2/core/Lenses : java/lang/annotation/Annotation { } +public abstract interface annotation class dev/fritz2/core/NoLens : java/lang/annotation/Annotation { +} + public final class dev/fritz2/core/RootInspector : dev/fritz2/core/Inspector { public fun (Ljava/lang/Object;)V public fun getData ()Ljava/lang/Object; diff --git a/core/src/commonMain/kotlin/dev/fritz2/core/lens.kt b/core/src/commonMain/kotlin/dev/fritz2/core/lens.kt index bafbd216d..b2b46f7c2 100644 --- a/core/src/commonMain/kotlin/dev/fritz2/core/lens.kt +++ b/core/src/commonMain/kotlin/dev/fritz2/core/lens.kt @@ -6,6 +6,15 @@ package dev.fritz2.core @Target(AnnotationTarget.CLASS) annotation class Lenses +/** + * Used by the fritz2 gradle-plugin to identify properties in sealed classes or interfaces, that should get ignored + * by the lens generation. + * + * Typical use case are const properties, that are overridden inside the data class body and not the ctor. + */ +@Target(AnnotationTarget.PROPERTY) +annotation class NoLens + /** * Describes a focus point into a data structure, i.e. a property of a given complex entity for read and write * access. diff --git a/lenses-annotation-processor/src/jvmMain/kotlin/dev/fritz2/lens/LenseablePropertiesDeterminer.kt b/lenses-annotation-processor/src/jvmMain/kotlin/dev/fritz2/lens/LenseablePropertiesDeterminer.kt index 11fc8169c..63652bdcf 100644 --- a/lenses-annotation-processor/src/jvmMain/kotlin/dev/fritz2/lens/LenseablePropertiesDeterminer.kt +++ b/lenses-annotation-processor/src/jvmMain/kotlin/dev/fritz2/lens/LenseablePropertiesDeterminer.kt @@ -4,6 +4,7 @@ import com.google.devtools.ksp.getDeclaredProperties import com.google.devtools.ksp.isPublic import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import dev.fritz2.core.NoLens internal fun interface LenseablePropertiesDeterminer { fun determine(classDeclaration: KSClassDeclaration): List @@ -15,5 +16,8 @@ internal val determineLensablePropertiesInConstructor = LenseablePropertiesDeter .filter { it.isPublic() && allPublicCtorProps.contains(it.simpleName) }.toList() } internal val determineLensablePropertiesInBody = LenseablePropertiesDeterminer { classDeclaration -> - classDeclaration.getDeclaredProperties().filter { it.isPublic() }.toList() + classDeclaration.getDeclaredProperties() + .filter { it.isPublic() } + .filter { it.annotations.none { annotation -> annotation.shortName.asString() == NoLens::class.simpleName } } + .toList() } \ No newline at end of file diff --git a/lenses-annotation-processor/src/jvmTest/kotlin/dev/fritz2/lens/LensesProcessorTests.kt b/lenses-annotation-processor/src/jvmTest/kotlin/dev/fritz2/lens/LensesProcessorTests.kt index 1c7b0f052..e786c82a4 100644 --- a/lenses-annotation-processor/src/jvmTest/kotlin/dev/fritz2/lens/LensesProcessorTests.kt +++ b/lenses-annotation-processor/src/jvmTest/kotlin/dev/fritz2/lens/LensesProcessorTests.kt @@ -533,6 +533,24 @@ class LensesProcessorTests { ) } + @ExperimentalPathApi + @Suppress("UNUSED_PARAMETER") + @DisplayName("validate NoLens annotation in sealed base class or interface will not create lens for annotated properties") + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("createNoLensAnnotatedClasses") + fun validateNoLensAnnotationWorks(description: String, kotlinSource: SourceFile, expectedCode: String) { + val compilationResult = compileSource(kotlinSource) + + assertAll( + { assertThat(compilationResult.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) }, + { + assertThat(compilationResult.kspGeneratedSources.find { it.name == "FrameworkLenses.kt" }) + .usingCharset(StandardCharsets.UTF_8) + .hasContent(expectedCode) + } + ) + } + companion object { private val validCodeForSealedHierarchies = """ |// GENERATED by fritz2 - NEVER CHANGE CONTENT MANUALLY! @@ -1222,5 +1240,114 @@ class LensesProcessorTests { ) ) ) + + private val validCodeForNoLensResult = """ + |// GENERATED by fritz2 - NEVER CHANGE CONTENT MANUALLY! + |package dev.fritz2.lenstest + | + |import dev.fritz2.core.Lens + |import dev.fritz2.core.lensOf + |import kotlin.String + | + |public fun Framework.Companion.foo(): Lens = lensOf( + | "foo", + | { parent -> + | when(parent) { + | is Fritz2 -> parent.foo + | is Spring -> parent.foo + | } + | }, + | { parent, value -> + | when(parent) { + | is Fritz2 -> parent.copy(foo = value) + | is Spring -> parent.copy(foo = value) + | } + | } + |) + | + |public fun Lens.foo(): Lens = this + Framework.foo() + | + |public fun Framework.Companion.fritz2(): Lens = lensOf( + | "", + | { it as Fritz2 }, + | { _, v -> v } + |) + | + |public fun Framework.Companion.spring(): Lens = lensOf( + | "", + | { it as Spring }, + | { _, v -> v } + |) + """.trimMargin() + + + @JvmStatic + fun createNoLensAnnotatedClasses() = listOf( + arguments( + "sealed class", + SourceFile.kotlin( + "sealedClassesForNoLensesTests.kt", + """ + package dev.fritz2.lenstest + + import dev.fritz2.core.Lenses + + @Lenses + sealed class Framework { + @NoLens + val ignore: String + abstract val foo: String + companion object + } + + data class Fritz2 ( + override val foo: String, + ) : Framework { + override val ignore: String = "Fritz2" + } + + data class Spring ( + override val foo: String, + ) : Framework { + override val ignore: String = "Spring" + } + """ + ), + validCodeForNoLensResult + ), + arguments( + "sealed interface", + SourceFile.kotlin( + "sealedInterfacesForNoLensesTests.kt", + """ + package dev.fritz2.lenstest + + import dev.fritz2.core.Lenses + + @Lenses + sealed interface Framework { + @NoLens + val ignore: String + val foo: String + + companion object + } + + data class Fritz2 ( + override val foo: String, + ) : Framework { + override val ignore: String = "Fritz2" + } + + data class Spring ( + override val foo: String, + ) : Framework { + override val ignore: String = "Spring" + } + """ + ), + validCodeForNoLensResult + ) + ) } } \ No newline at end of file