Skip to content
Draft
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
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build test
- name: Detekt
run: ./gradlew detekt
- name: Upload API
uses: actions/upload-artifact@v1
with:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.onecode.compass.api

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
@Repeatable
annotation class DeepLink(val schema: String, val host: String, val path: String = "")
19 changes: 17 additions & 2 deletions compass/ksp/src/main/kotlin/de/onecode/compass/ksp/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSType
import de.onecode.compass.api.DeepLink
import de.onecode.compass.api.Destination
import de.onecode.compass.api.Parameter
import de.onecode.compass.api.SubGraph
import de.onecode.compass.ksp.descriptions.DeepLinkDescription
import de.onecode.compass.ksp.descriptions.DestinationDescription
import de.onecode.compass.ksp.descriptions.ParameterDescription
import de.onecode.compass.ksp.descriptions.SubGraphDescription
Expand Down Expand Up @@ -42,8 +45,8 @@ fun KSType.asClassDeclaration(): KSClassDeclaration =
?: error("${declaration.simpleName.asString()} has to be a class, an interface or an object")

fun KSAnnotation.toParameterDescription(classDeclaration: KSClassDeclaration): ParameterDescription {
val paramName = getParameterValue<String>(ParameterDescription::name.name, classDeclaration)
val paramType = getParameterValue<KSType>(ParameterDescription::type.name, classDeclaration)
val paramName = getParameterValue<String>(Parameter::name.name, classDeclaration)
val paramType = getParameterValue<KSType>(Parameter::type.name, classDeclaration)

return ParameterDescription(
name = paramName,
Expand All @@ -52,6 +55,18 @@ fun KSAnnotation.toParameterDescription(classDeclaration: KSClassDeclaration): P
)
}

fun KSAnnotation.toDeepLinkDescription(classDeclaration: KSClassDeclaration): DeepLinkDescription {
val schema = getParameterValue<String>(DeepLink::schema.name, classDeclaration)
val host = getParameterValue<String>(DeepLink::host.name, classDeclaration)
val path = getParameterValue<String>(DeepLink::path.name, classDeclaration)

return DeepLinkDescription(
schema = schema,
host = host,
path = path
)
}

fun <T : Annotation> KSDeclaration.filterAnnotations(annotationClass: KClass<T>): Sequence<KSAnnotation> =
annotations.filter { it.shortName.asString() == annotationClass.simpleName }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.onecode.compass.ksp.descriptions

data class DeepLinkDescription(
val schema: String,
val host: String,
val path: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ data class DestinationDescription(
override val name: String,
override val parameters: List<ParameterDescription>,
val navigationTargets: List<NavigationTarget>,
val deepLinks: List<DeepLinkDescription>,
val isHome: Boolean,
val isTop: Boolean,
) : NamedWithParameters
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.squareup.kotlinpoet.ksp.toClassName
import de.onecode.compass.api.DeepLink
import de.onecode.compass.api.Destination
import de.onecode.compass.api.Home
import de.onecode.compass.api.Navigation
Expand All @@ -19,6 +20,7 @@ import de.onecode.compass.ksp.getDestinationName
import de.onecode.compass.ksp.getParameterValue
import de.onecode.compass.ksp.getSubGraphName
import de.onecode.compass.ksp.isNavigable
import de.onecode.compass.ksp.toDeepLinkDescription
import de.onecode.compass.ksp.toParameterDescription

class DestinationVisitor : KSVisitorVoid() {
Expand Down Expand Up @@ -71,10 +73,16 @@ class DestinationVisitor : KSVisitorVoid() {
parameterAnnotation.toParameterDescription(classDeclaration)
}

val deeplinks = classDeclaration.filterAnnotations(DeepLink::class)
.map { deeplinkAnnotation ->
deeplinkAnnotation.toDeepLinkDescription(classDeclaration)
}

_destinations += DestinationDescription(
name = destinationName,
parameters = parameters.toList(),
navigationTargets = navTargets.toList(),
deepLinks = deeplinks.toList(),
isHome = isHome,
isTop = isTop
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ private fun createFileSpec(graph: GraphDescription): FileSpec.Builder {
if (destinations.containsParameters() || subGraphs.hasParametrizedDestinations()) {
addImport("androidx.navigation", *navigationImports)
}
if (destinations.containsDeepLinks() || subGraphs.hasDestinationsWithDeepLinks()) {
addImport("androidx.navigation", "NavDeepLink")
}
}

return fileSpec
Expand Down Expand Up @@ -106,7 +109,13 @@ private fun FileSpec.Builder.addParameterExtensionsOnSavedStateHandle(destinatio
}

private fun List<SubGraphDescription>.hasParametrizedDestinations(): Boolean =
any { subGraph -> subGraph.destinations.any { it.parameters.isNotEmpty() } }
any { it.destinations.containsParameters() }

private fun List<DestinationDescription>.containsParameters(): Boolean =
any { it.parameters.isNotEmpty() }

private fun List<SubGraphDescription>.hasDestinationsWithDeepLinks(): Boolean =
any { it.destinations.containsDeepLinks() }

private fun List<DestinationDescription>.containsDeepLinks(): Boolean =
any { it.deepLinks.isNotEmpty() }
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.buildCodeBlock
import de.onecode.compass.ksp.decapitalize
import de.onecode.compass.ksp.descriptions.DeepLinkDescription
import de.onecode.compass.ksp.descriptions.DestinationDescription
import de.onecode.compass.ksp.descriptions.ParameterDescription
import de.onecode.compass.ksp.generator.LOCAL_NAV_HOST_CONTROLLER
import de.onecode.compass.ksp.generator.contextName
import de.onecode.compass.ksp.route
Expand Down Expand Up @@ -44,14 +46,20 @@ private fun DestinationDescription.toNavigationComposableCodeBlock(): CodeBlock

private fun buildComposableCodeBlock(destination: DestinationDescription, statements: CodeBlock.Builder.() -> Unit): CodeBlock =
buildCodeBlock {
beginControlFlow("composable(route = %S, arguments = %L)", destination.route, navigationArgumentsCodeBlock(destination))
val arguments = navigationArgumentsCodeBlock(destination.parameters)
val deepLinks = deepLinksCodeBlock(destination.deepLinks, destination.parameters)
beginControlFlow(
"composable(route = %S, arguments = %L, deepLinks = %L)",
destination.route,
arguments,
deepLinks
)
statements()
endControlFlow()
}

private fun navigationArgumentsCodeBlock(description: DestinationDescription): CodeBlock {
val parameters = description.parameters
return buildCodeBlock {
private fun navigationArgumentsCodeBlock(parameters: List<ParameterDescription>): CodeBlock =
buildCodeBlock {
if (parameters.isEmpty()) {
addStatement("emptyList()")
} else {
Expand All @@ -67,4 +75,36 @@ private fun navigationArgumentsCodeBlock(description: DestinationDescription): C
addStatement("listOf(%L)", blocks.joinToString())
}
}

private fun deepLinksCodeBlock(deepLinks: List<DeepLinkDescription>, parameters: List<ParameterDescription>): CodeBlock =
buildCodeBlock {
if (deepLinks.isEmpty()) {
addStatement("emptyList()")
} else {
val parametersSuffix = parameters.joinToString(separator = "/") { "{${it.name}}" }
val navDeepLink = deepLinks.map { description ->
val deepLink = description.toUri(parametersSuffix)
buildCodeBlock {
addStatement("NavDeepLink(%S)", deepLink)
}
}

addStatement("listOf(%L)", navDeepLink.joinToString())
}
}

private fun DeepLinkDescription.toUri(parametersSuffix: String): String {
val uri = "${schema}://${host}"
return if (path.isBlank()) {
uri
} else {
"$uri/$path"
}.let {
if (parametersSuffix.isBlank()) {
it
} else {
"$it/$parametersSuffix"
}
}
}

Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
package de.onecode.compass.ksp

import com.google.common.truth.Truth.assertThat
import de.onecode.compass.ksp.descriptions.DestinationDescription
import de.onecode.compass.ksp.descriptions.GraphDescription
import de.onecode.compass.ksp.descriptions.SubGraphDescription
import de.onecode.compass.ksp.generator.common.destinationDescription
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class CheckGraphStateTest {
@Test
fun `Fail because of multiple homes`() {
val description1 = DestinationDescription(
val description1 = destinationDescription(
name = "foo",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
)
val description2 = DestinationDescription(
val description2 = destinationDescription(
name = "bar",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
)
val graph = GraphDescription(listOf(description1, description2), emptyList())

Expand All @@ -33,19 +27,12 @@ class CheckGraphStateTest {

@Test
fun `Standard graph`() {
val description1 = DestinationDescription(
val description1 = destinationDescription(
name = "foo",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
)
val description2 = DestinationDescription(
val description2 = destinationDescription(
name = "bar",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
)
val graph = GraphDescription(listOf(description1, description2), emptyList())

Expand All @@ -57,19 +44,11 @@ class CheckGraphStateTest {

@Test
fun `No home graph`() {
val description1 = DestinationDescription(
val description1 = destinationDescription(
name = "foo",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
)
val description2 = DestinationDescription(
val description2 = destinationDescription(
name = "bar",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
)
val graph = GraphDescription(listOf(description1, description2), emptyList())

Expand All @@ -91,19 +70,13 @@ class CheckGraphStateTest {

@Test
fun `Subgraph with multiple homes`() {
val sub1 = DestinationDescription(
val sub1 = destinationDescription(
name = "sub1",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
)
val sub2 = DestinationDescription(
val sub2 = destinationDescription(
name = "sub2",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
)
val sub = SubGraphDescription("sub", listOf(sub1, sub2))
val graph = GraphDescription(emptyList(), listOf(sub))
Expand All @@ -115,19 +88,11 @@ class CheckGraphStateTest {

@Test
fun `Subgraph with no home`() {
val sub1 = DestinationDescription(
val sub1 = destinationDescription(
name = "sub1",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
)
val sub2 = DestinationDescription(
val sub2 = destinationDescription(
name = "sub2",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = false
)
val sub = SubGraphDescription("sub", listOf(sub1, sub2))
val graph = GraphDescription(emptyList(), listOf(sub))
Expand All @@ -139,18 +104,12 @@ class CheckGraphStateTest {

@Test
fun `Subgraph with unallowed tob destination`() {
val sub1 = DestinationDescription(
val sub1 = destinationDescription(
name = "sub1",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = true,
isTop = false
)
val sub2 = DestinationDescription(
val sub2 = destinationDescription(
name = "sub2",
parameters = emptyList(),
navigationTargets = emptyList(),
isHome = false,
isTop = true
)
val sub = SubGraphDescription("sub", listOf(sub1, sub2))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package de.onecode.compass.ksp.discovery

import com.google.common.truth.Truth.assertThat
import de.onecode.compass.ksp.descriptions.DestinationDescription
import de.onecode.compass.ksp.generator.common.destinationDescription
import org.junit.jupiter.api.Test

class GraphVisitorTest {
@Test
fun `create graph description without sub graph`() {
val destinations = listOf(
DestinationDescription(name = "D1", parameters = emptyList(), navigationTargets = emptyList(), isHome = true, isTop = false),
DestinationDescription(name = "D2", parameters = emptyList(), navigationTargets = emptyList(), isHome = false, isTop = false)
destinationDescription(
name = "D1",
isHome = true,
),
destinationDescription(
name = "D2",
)
)
val graphVisitor = GraphVisitor(destinations)

Expand All @@ -23,11 +28,21 @@ class GraphVisitorTest {
@Test
fun `create graph description with sub graph`() {

val s1 = DestinationDescription(name = "S1", parameters = emptyList(), navigationTargets = emptyList(), isHome = true, isTop = false)
val s2 = DestinationDescription(name = "S2", parameters = emptyList(), navigationTargets = emptyList(), isHome = false, isTop = false)
val s1 = destinationDescription(
name = "S1",
isHome = true,
)
val s2 = destinationDescription(
name = "S2",
)
val destinations = listOf(
DestinationDescription(name = "D1", parameters = emptyList(), navigationTargets = emptyList(), isHome = true, isTop = false),
DestinationDescription(name = "D2", parameters = emptyList(), navigationTargets = emptyList(), isHome = false, isTop = false),
destinationDescription(
name = "D1",
isHome = true,
),
destinationDescription(
name = "D2",
),
s1,
s2
)
Expand Down
Loading