Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new @NoLens annotation #886

Merged
merged 3 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> (Ljava/lang/Object;)V
public fun getData ()Ljava/lang/Object;
Expand Down
9 changes: 9 additions & 0 deletions core/src/commonMain/kotlin/dev/fritz2/core/lens.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<KSPropertyDeclaration>
Expand All @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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<Framework, String> = 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 <PARENT> Lens<PARENT, Framework>.foo(): Lens<PARENT, String> = this + Framework.foo()
|
|public fun Framework.Companion.fritz2(): Lens<Framework, Fritz2> = lensOf(
| "",
| { it as Fritz2 },
| { _, v -> v }
|)
|
|public fun Framework.Companion.spring(): Lens<Framework, Spring> = 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
)
)
}
}
Loading