2

我正在尝试编写我的第一个 Lint 规则。现在我只想检测注释的使用@AnyThread

我创建了一个模块来实现我的自定义规则。该模块的 gradle 文件是(我使用 gradle 插件版本 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")
    }
}

我的探测器是:

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"
      )
  }
}

IssueRegistry的是:

class MyIssueRegistry : IssueRegistry() {
    override val issues: List<Issue>
        get() = listOf(
            AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)

    override val api: Int = CURRENT_API
}

我写了一些测试:


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()
              )
      }
    }

在哪里Stubs.ANYTHREAD_EXPERIMENT

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")
}

我所有的测试都失败了(除了noAnnotatedFileKotlin),实际上如果我在调试模式下对测试的调用设置断点就context.report永远不会暂停,这意味着androidx.annotation.AnyThread永远不会检测到注释。

会出什么问题?我错过了什么?

我看过并阅读了一些文档:

我通过实现NoisyDetector谈话编码风格中给出的控制配置:使用自定义 Lint 规则进行静态分析,测试结果很好。

4

2 回答 2

1

我回答这个问题可能有点晚了,但它可能对遇到这个问题的其他人有用

我遇到了同样的问题,我需要找到 an 的用法Annotation并报告它们。但由于某种原因,Kotlin UAST(Java 工作正常)不记录/报告注释。我正在使用一种解决方法来解决这个问题

解决方法


我没有访问注释,而是访问UMethodUClass取决于您的需要。然后我正在手动String.contains()检查node.sourcePsi.text注释是否存在

    override fun getApplicableUastTypes() = listOf(UMethod::class.java)

    override fun createUastHandler(context: JavaContext): UElementHandler {
        return object : UElementHandler() {

            override fun visitMethod(node: UMethod) {
                if (!shouldSkip(node.sourcePsi) && node.isAnnotatedWith("AnyThread")) {
                    context.report(
                        issue = ANYTHREAD_ANNOTATION_ISSUE,
                        scope = usage,
                        location = context.getNameLocation(usage),
                        message = "A message"
                    )
                }
            }
        }

        // Skip KtClass, because it calls the `visitMethod` method since the class has the constructor method in it
        private fun shouldSkip(node: PsiElement?): Boolean = node is KtClass
    }

    fun UAnnotated.isAnnotatedWith(annotation: String) = sourcePsi?.text?.contains("@$annotation") == true

缺点


我看到的问题是它将为每个方法调用它,而不是仅在找到注释时调用,并且该shouldSkip()方法对我来说似乎是一个 hack。但除此之外它可以正常工作并且应该报告问题

注意:调用node.hasAnnotation(), node.findAnnotation()orcontext.evaluator.hasAnnotation()不会在 Kotlin 中找到注解

于 2020-09-24T09:08:30.747 回答
0

@AnyThread您可以通过添加SUPPORT_ANNOTATIONS_JAR到调用或在单独的测试源文件中lint().files(...)手动声明注释类来为注释添加存根。@AnyThread

可以在此处SUPPORT_ANNOTATIONS_JAR找到使用inside of的示例。CheckResultDetectorTest

于 2021-11-17T20:37:09.800 回答