I am trying to write my first Lint rule. For now I just want to detect the
use of the annotation @AnyThread
.
I have created a module to implement my custom rule. The gradle file for this module is (I use the gradle plugin version 3.6.1):
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
compileOnly 'com.android.tools.lint:lint-api:26.6.1'
compileOnly 'com.android.tools.lint:lint-checks:26.6.1'
testImplementation "com.android.tools.lint:lint:26.6.1"
testImplementation "com.android.tools.lint:lint-tests:26.6.1"
testImplementation "com.android.tools:testutils:26.6.1"
testImplementation "junit:junit:4.12"
}
jar {
manifest {
attributes("Lint-Registry-v2": "com.test.lint.MyIssueRegistry")
}
}
My detector is:
package com.test.lint
//...
class AnyThreadAnnotationDetector: AbstractAnnotationDetector(), Detector.UastScanner {
companion object {
private const val AnyThreadId = "AnyThreadId"
const val AnyThreadDescription = "This is an attempt to find AnyThread annotation in code"
const val AnyThreadExplanation = "AnyThread annotation found!"
val ANYTHREAD_ANNOTATION_ISSUE = Issue.create(
id = AnyThreadId,
briefDescription = AnyThreadDescription,
explanation = AnyThreadExplanation,
category = Category.CORRECTNESS,
priority = 4,
severity = Severity.INFORMATIONAL,
implementation = Implementation(
AnyThreadAnnotationDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
override fun applicableAnnotations(): List<String>? = listOf("androidx.annotation.AnyThread")
override fun visitAnnotationUsage(
context: JavaContext,
usage: UElement,
type: AnnotationUsageType,
annotation: UAnnotation,
qualifiedName: String,
method: PsiMethod?,
annotations: List<UAnnotation>,
allMemberAnnotations: List<UAnnotation>,
allClassAnnotations: List<UAnnotation>,
allPackageAnnotations: List<UAnnotation>
) {
context.report(
issue = ANYTHREAD_ANNOTATION_ISSUE,
scope = usage,
location = context.getNameLocation(usage),
message = "A message"
)
}
}
My IssueRegistry
is:
class MyIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(
AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
override val api: Int = CURRENT_API
}
I wrote some tests:
class AnyThreadAnnotationDetectorTest {
@Test
fun noAnnotatedFileKotlin() {
TestLintTask.lint()
.files(
LintDetectorTest.kotlin(
"""
|package foo;
|
|class XmlHttpRequest {
|}""".trimMargin()
)
)
.allowMissingSdk()
.issues(AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
.run()
.expectClean()
}
@Test
fun annotatedKotlinMethod() {
TestLintTask.lint()
.files(
LintDetectorTest.kotlin(
"""
|package foo;
|
|import androidx.annotation.AnyThread
|
|class XmlHttpRequest {
|@AnyThread
|fun test(){}
|}""".trimMargin()
)
)
.allowMissingSdk()
.issues(AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
.run()
.expect(
"""
Just a test to find annotations
0 errors, 0 warnings
""".trimIndent()
)
}
@Test
fun testNoisyDetector() {
TestLintTask.lint().files(Stubs.ANYTHREAD_EXPERIMENT)
.allowMissingSdk()
.issues(AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
.run()
.expect(
"""
Just a test to find annotations
0 errors, 0 warnings
""".trimIndent()
)
}
}
Where the Stubs.ANYTHREAD_EXPERIMENT
is:
object Stubs {
val ANYTHREAD_EXPERIMENT = kotlin(
"com/test/applicationlintdemoapp/AnythreadAnnotationStubs.kt",
"""
package com.test.applicationlintdemoapp
import androidx.annotation.AnyThread
class AnythreadClassExperiment {
@AnyThread
fun setTimeToNow() {
TimeTravelProvider().setTime(System.currentTimeMillis())
}
@AnyThread
fun setTimeToEpoch() {
TimeTravelProvider().setTime(0)
}
fun violateTimeTravelAccords() {
TimeTravelProvider().setTime(-1)
}
}
"""
).indented().within("src")
}
All my test fail (except noAnnotatedFileKotlin
), actually if I put a breakpoint on
the call to context.report
the test made in debug mode is never paused, meaning
that the annotation androidx.annotation.AnyThread
is never detected.
What could go wrong ? what did I miss?
I have seen and read a some docs:
- Writing custom lint rules
- KotlinConf 2017 - Kotlin Static Analysis with Android Lint
- Writing your first Lint check
- Making Custom Lint for Kotlin Code
- Getting the Most Out of Android Lint
- Coding in Style: Static Analysis with Custom Lint Rules
And I controlled the configuration by implementing the NoisyDetector
given
in the talk Coding in Style: Static Analysis with Custom Lint Rules, the result
of the test are fine.