Skip to content

Commit

Permalink
added annotation to ignore @Preview compose functions, tried to fix j…
Browse files Browse the repository at this point in the history
…acoco for :core-ui module but failed
  • Loading branch information
rygelouv committed Dec 19, 2023
1 parent 39f80ee commit dfb57e4
Show file tree
Hide file tree
Showing 29 changed files with 288 additions and 12 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/app/books/tanga/feature/auth/AuthScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.books.tanga.R
import app.books.tanga.common.ui.ProgressState
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.theme.LocalSpacing
import app.books.tanga.coreui.theme.TangaTheme

Expand Down Expand Up @@ -208,6 +209,7 @@ private fun WelcomeMessageColumn(modifier: Modifier = Modifier) {

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun AuthScreenPreview() {
val state = AuthUiState(
googleSignInButtonProgressState = ProgressState.Hide
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/app/books/tanga/feature/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.books.tanga.R
import app.books.tanga.common.ui.ProgressState
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.components.ProfileImage
import app.books.tanga.coreui.icons.TangaIcons
import app.books.tanga.coreui.theme.LocalSpacing
Expand Down Expand Up @@ -255,6 +256,7 @@ private fun HomeSectionPreview() {

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun HomeScreenPreview() {
HomeScreen({}, {}, onSummaryClick = {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.books.tanga.R
import app.books.tanga.common.ui.ProgressState
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.theme.LocalSpacing
import app.books.tanga.entity.SummaryId
import app.books.tanga.feature.summary.list.SummaryItem
Expand Down Expand Up @@ -106,6 +107,7 @@ fun FavoriteGrid(

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun LibraryScreenPreview() {
LibraryScreen(
onExploreButtonClick = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import app.books.tanga.R
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.components.TangaButtonRightIcon
import app.books.tanga.coreui.icons.TangaIcons
import app.books.tanga.coreui.theme.TangaTheme
import app.books.tanga.feature.auth.toAuthentication
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
Expand All @@ -38,13 +40,6 @@ import kotlinx.coroutines.launch

const val MAX_PAGER_INDEX = 3

@Preview
@Composable
private fun OnboardingScreenPreview() {
val navController = rememberNavController()
OnboardingScreen(navController)
}

@OptIn(ExperimentalPagerApi::class)
@Composable
fun OnboardingScreen(
Expand Down Expand Up @@ -155,3 +150,13 @@ fun FinishOnboardingButton(
}
}
}

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun OnboardingScreenPreview() {
val navController = rememberNavController()

Check warning on line 158 in app/src/main/java/app/books/tanga/feature/onboarding/OnboardingScreen.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/app/books/tanga/feature/onboarding/OnboardingScreen.kt#L158

Added line #L158 was not covered by tests
TangaTheme {
OnboardingScreen(navController)
}

Check warning on line 161 in app/src/main/java/app/books/tanga/feature/onboarding/OnboardingScreen.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/app/books/tanga/feature/onboarding/OnboardingScreen.kt#L161

Added line #L161 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import app.books.tanga.R
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.icons.TangaIcons
import app.books.tanga.coreui.theme.Cerulean
import app.books.tanga.coreui.theme.Cultured
import app.books.tanga.coreui.theme.LocalGradientColors
import app.books.tanga.coreui.theme.LocalSpacing
import app.books.tanga.coreui.theme.TangaTheme
import com.google.accompanist.systemuicontroller.rememberSystemUiController

@Composable
Expand Down Expand Up @@ -251,3 +254,13 @@ private fun BestValueLabel() {
)
}
}

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun PricingPlanScreenPreview() {
TangaTheme {
PricingPlanScreen {
}
}

Check warning on line 265 in app/src/main/java/app/books/tanga/feature/pricing/PricingPlanScreen.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/java/app/books/tanga/feature/pricing/PricingPlanScreen.kt#L264-L265

Added lines #L264 - L265 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.books.tanga.R
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.components.ProfileImage
import app.books.tanga.coreui.components.TangaButton
import app.books.tanga.coreui.theme.TangaTheme
Expand Down Expand Up @@ -219,6 +220,7 @@ fun ProfileHeader(

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun ProfileScreenPreview() {
TangaTheme {
ProfileScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import androidx.compose.ui.unit.dp
import app.books.tanga.R
import app.books.tanga.common.ui.ProgressState
import app.books.tanga.common.ui.UrlDownloadableImage
import app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport
import app.books.tanga.coreui.components.ExpendableText
import app.books.tanga.coreui.components.ProfileImage
import app.books.tanga.coreui.components.SummaryActionButton
Expand Down Expand Up @@ -403,6 +404,7 @@ fun Recommendations(

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
private fun SummaryDetailsScreenPreview() {
TangaTheme {
SummaryDetailsScreen(
Expand Down
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import com.android.build.gradle.internal.crash.afterEvaluate

buildscript {
// Hilt*/
Expand All @@ -10,6 +11,8 @@ buildscript {
classpath("org.jacoco:org.jacoco.core:0.8.11")
}
}

@Suppress("DSL_SCOPE_VIOLATION")
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
Expand Down
10 changes: 9 additions & 1 deletion buildscripts/detekt.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ apply plugin: "io.gitlab.arturbosch.detekt"
detekt {
buildUponDefaultConfig = true
parallel = true
config.setFrom(files("${rootProject.rootDir}/config/detekt/detekt.yml", "${rootProject.rootDir}/config/detekt/compose-detekt.yml"))
config.setFrom(files(
"${rootProject.rootDir}/config/detekt/detekt.yml",
"${rootProject.rootDir}/config/detekt/compose-detekt.yml"),
"${rootProject.rootDir}/code-checks/src/main/resources/config/config.yml"
)
// We remove this until we figure out how to create a single baseline for all modules
// baseline = file("${rootProject.rootDir}/config/detekt/baseline.xml")
}

tasks.named("detekt").configure {
onlyIf {
(project.name != 'code-checks')
}
reports {
xml.required.set(true)
xml.outputLocation.set(file("build/reports/detekt.xml"))
Expand All @@ -25,4 +32,5 @@ dependencies {
detekt libs.detekt.cli
// detektPlugins libs.twitter.compose.lint.rules
detektPlugins libs.detekt.rules.compose
detektPlugins project(":code-checks")
}
20 changes: 17 additions & 3 deletions buildscripts/jacoco.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,24 @@ tasks.register<JacocoReport>("jacocoTestReport") {
"**/*\$Result.*",
"**/*\$Result$*.*",
)

val debugTree = fileTree(mapOf("dir" to "$buildDir/intermediates/classes/debug", "excludes" to fileFilter))
val mainSrc = "${project.projectDir}/src/main/java"
val javaTree = fileTree(mapOf("dir" to "$buildDir/intermediates/javac/debug/classes", "excludes" to fileFilter))
val kotlinTree = fileTree(mapOf("dir" to "$buildDir/tmp/kotlin-classes/debug", "excludes" to fileFilter))

val appSrc = "${project.rootDir}/app/src/main/java"
val coreUiSrc = "${project.rootDir}/core-ui/src/main/java"

val appClassDirs = files(debugTree, javaTree, kotlinTree)
val coreUiClassDirs = files(
fileTree(mapOf("dir" to "${project.rootDir}/core-ui/build/intermediates/javac/debug/classes", "excludes" to fileFilter)),
fileTree(mapOf("dir" to "${project.rootDir}/core-ui/build/tmp/kotlin-classes/debug", "excludes" to fileFilter)),
fileTree(mapOf("dir" to "${project.rootDir}/core-ui/build/intermediates/classes/debug", "excludes" to fileFilter))
)

sourceDirectories.setFrom(files(appSrc, coreUiSrc))
classDirectories.setFrom(files(appClassDirs, coreUiClassDirs))

sourceDirectories.setFrom(files(mainSrc))
classDirectories.setFrom(files(debugTree))
executionData.setFrom(
fileTree(
mapOf(
Expand All @@ -59,4 +72,5 @@ tasks.register<JacocoReport>("jacocoTestReport") {
println("Wrote HTML coverage report to ${reports.html.entryPoint}")
println("Wrote XML coverage report to ${reports.xml.name}")
}

}
1 change: 1 addition & 0 deletions code-checks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
24 changes: 24 additions & 0 deletions code-checks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("java-library")
id("kotlin")
alias(libs.plugins.detekt) apply false
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
compileOnly(libs.detekt.api)
compileOnly(libs.lint.api)
}

tasks.jar {
manifest {
// Format is
// attributes(mapOf("Lint-Registry-v2" to "<fully-qualified-class-name-of-your-issue-registry>"))
attributes(mapOf("Lint-Registry-v2" to "app.books.tanga.codechecks.registry.LintRegistry"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package app.books.tanga.codechecks

import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.TextFormat
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod

/**
* A custom lint detector that checks for missing `@ExcludeFromJacocoGeneratedReport`
* annotations on Jetpack Compose preview methods.
*
* This detector scans through method declarations in Kotlin files and reports any `@Preview` annotated methods
* that are not also annotated with `@ExcludeFromJacocoGeneratedReport`. This ensures that preview methods
* are properly excluded from JaCoCo coverage reports.
*
* Taken from here: https://github.com/AdamMc331/TOA/blob/96248ce1e8c27817779d4785a2f73d4100a1ea90/lint-checks/src/main/java/com/adammcneilly/toa/lint/MissingExcludePreviewAnnotationDetector.kt
*/
@Suppress("UnstableApiUsage")
class MissingExcludePreviewAnnotationDetector : Detector(), Detector.UastScanner {

/**
* Specifies that this detector is interested in method declarations.
*/
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UMethod::class.java)

/**
* Creates a handler for processing UAST method nodes.
*/
override fun createUastHandler(context: JavaContext): UElementHandler = PreviewMethodElementHandler(context)

/**
* An inner class to handle method nodes detected by the detector.
*/
private class PreviewMethodElementHandler(private val context: JavaContext) : UElementHandler() {

/**
* Visits each method node in the UAST.
*
* Checks if the method is a Compose preview method and whether it's properly
* annotated to be excluded from JaCoCo reports.
*/
override fun visitMethod(node: UMethod) {
val isComposePreviewMethod = node.hasAnnotation(COMPOSE_PREVIEW_ANNOTATION)
val isExcludedFromJacoco = node.hasAnnotation(EXCLUDE_FROM_JACOCO_ANNOTATION)

if (isComposePreviewMethod && isExcludedFromJacoco.not()) {
context.report(
issue = ISSUE_MISSING_EXCLUDE_PREVIEW_ANNOTATION,
location = context.getLocation(node),
message = ISSUE_MISSING_EXCLUDE_PREVIEW_ANNOTATION.getExplanation(TextFormat.TEXT),
)
}
}
}

companion object {
private const val COMPOSE_PREVIEW_ANNOTATION = "androidx.compose.ui.tooling.preview.Preview"
private const val EXCLUDE_FROM_JACOCO_ANNOTATION =
"app.books.tanga.coreui.common.ExcludeFromJacocoGeneratedReport"

/**
* Definition of the lint issue checked by this detector.
*/
@Suppress("MaxLineLength")
internal val ISSUE_MISSING_EXCLUDE_PREVIEW_ANNOTATION = Issue.create(
id = "MissingExcludePreviewAnnotation",
briefDescription = "Jetpack Compose previews should be excluded from JaCoCo Reports.",
explanation = "Any methods annotated with @Preview should also have " +
"the @ExcludeFromJacocoGeneratedReport annotation.",
category = Category.CUSTOM_LINT_CHECKS,
severity = Severity.ERROR,
implementation = Implementation(
MissingExcludePreviewAnnotationDetector::class.java,
Scope.JAVA_FILE_SCOPE,
),
priority = 10,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package app.books.tanga.codechecks

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType

class PreviewAnnotationRule(config: Config) : Rule(config) {

override val issue = Issue(
javaClass.simpleName,
Severity.CodeSmell,
"Compose functions annotated with @Preview must also be annotated with @ExcludeFromJacocoGeneratedReport",
Debt.FIVE_MINS
)

override fun visitAnnotationEntry(annotationEntry: KtAnnotationEntry) {
super.visitAnnotationEntry(annotationEntry)

if (annotationEntry.shortName?.asString() != "Preview") return

val owner = annotationEntry.getStrictParentOfType<KtFunction>() ?: return
if (owner.annotationEntries.none { it.shortName?.asString() == "ExcludeFromJacocoGeneratedReport" }) {
report(
CodeSmell(
issue,
Entity.from(annotationEntry),
message = "The @Preview function `${owner.name}` should also be " +
"annotated with @ExcludeFromJacocoGeneratedReport"
)
)
}
}
}
Loading

0 comments on commit dfb57e4

Please sign in to comment.