Merge pull request #899 from SimonMarquis/lint-test-detector
Create `TestMethodDetector` Lint to detect common naming patterns we want to avoidpull/999/head
commit
f2bc315eaf
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.AnnotationInfo
|
||||
import com.android.tools.lint.detector.api.AnnotationUsageInfo
|
||||
import com.android.tools.lint.detector.api.Category.Companion.TESTING
|
||||
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.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope.JAVA_FILE
|
||||
import com.android.tools.lint.detector.api.Scope.TEST_SOURCES
|
||||
import com.android.tools.lint.detector.api.Severity.WARNING
|
||||
import com.android.tools.lint.detector.api.SourceCodeScanner
|
||||
import com.android.tools.lint.detector.api.TextFormat.RAW
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UElement
|
||||
import java.util.EnumSet
|
||||
import kotlin.io.path.Path
|
||||
|
||||
/**
|
||||
* A detector that checks for common patterns in naming the test methods:
|
||||
* - [detectPrefix] removes unnecessary "test" prefix in all unit test.
|
||||
* - [detectFormat] Checks the `given_when_then` format of Android instrumented tests (backticks are not supported).
|
||||
*/
|
||||
class TestMethodNameDetector : Detector(), SourceCodeScanner {
|
||||
|
||||
override fun applicableAnnotations() = listOf("org.junit.Test")
|
||||
|
||||
override fun visitAnnotationUsage(
|
||||
context: JavaContext,
|
||||
element: UElement,
|
||||
annotationInfo: AnnotationInfo,
|
||||
usageInfo: AnnotationUsageInfo,
|
||||
) {
|
||||
val method = usageInfo.referenced as? PsiMethod ?: return
|
||||
|
||||
method.detectPrefix(context, usageInfo)
|
||||
method.detectFormat(context, usageInfo)
|
||||
}
|
||||
|
||||
private fun JavaContext.isAndroidTest() = Path("androidTest") in file.toPath()
|
||||
|
||||
private fun PsiMethod.detectPrefix(
|
||||
context: JavaContext,
|
||||
usageInfo: AnnotationUsageInfo,
|
||||
) {
|
||||
if (!name.startsWith("test")) return
|
||||
context.report(
|
||||
issue = PREFIX,
|
||||
scope = usageInfo.usage,
|
||||
location = context.getNameLocation(this),
|
||||
message = PREFIX.getBriefDescription(RAW),
|
||||
quickfixData = LintFix.create()
|
||||
.name("Remove prefix")
|
||||
.replace().pattern("""test[\s_]*""")
|
||||
.with("")
|
||||
.autoFix()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun PsiMethod.detectFormat(
|
||||
context: JavaContext,
|
||||
usageInfo: AnnotationUsageInfo,
|
||||
) {
|
||||
if (!context.isAndroidTest()) return
|
||||
if ("""[^\W_]+(_[^\W_]+){1,2}""".toRegex().matches(name)) return
|
||||
context.report(
|
||||
issue = FORMAT,
|
||||
scope = usageInfo.usage,
|
||||
location = context.getNameLocation(this),
|
||||
message = FORMAT.getBriefDescription(RAW),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun issue(
|
||||
id: String,
|
||||
briefDescription: String,
|
||||
explanation: String,
|
||||
): Issue = Issue.create(
|
||||
id = id,
|
||||
briefDescription = briefDescription,
|
||||
explanation = explanation,
|
||||
category = TESTING,
|
||||
priority = 5,
|
||||
severity = WARNING,
|
||||
implementation = Implementation(
|
||||
TestMethodNameDetector::class.java,
|
||||
EnumSet.of(JAVA_FILE, TEST_SOURCES),
|
||||
),
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val PREFIX: Issue = issue(
|
||||
id = "TestMethodPrefix",
|
||||
briefDescription = "Test method starts with `test`",
|
||||
explanation = "Test method should not start with `test`.",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val FORMAT: Issue = issue(
|
||||
id = "TestMethodFormat",
|
||||
briefDescription = "Test method does not follow the `given_when_then` or `when_then` format",
|
||||
explanation = "Test method should follow the `given_when_then` or `when_then` format.",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFile
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
|
||||
import com.google.samples.apps.nowinandroid.lint.TestMethodNameDetector.Companion.FORMAT
|
||||
import com.google.samples.apps.nowinandroid.lint.TestMethodNameDetector.Companion.PREFIX
|
||||
import org.junit.Test
|
||||
|
||||
class TestMethodNameDetectorTest {
|
||||
|
||||
@Test
|
||||
fun `detect prefix`() {
|
||||
lint().issues(PREFIX)
|
||||
.files(
|
||||
JUNIT_TEST_STUB,
|
||||
kotlin(
|
||||
"""
|
||||
import org.junit.Test
|
||||
class Test {
|
||||
@Test
|
||||
fun foo() = Unit
|
||||
@Test
|
||||
fun test_foo() = Unit
|
||||
@Test
|
||||
fun `test foo`() = Unit
|
||||
}
|
||||
""",
|
||||
).indented(),
|
||||
)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/Test.kt:6: Warning: Test method starts with test [TestMethodPrefix]
|
||||
fun test_foo() = Unit
|
||||
~~~~~~~~
|
||||
src/Test.kt:8: Warning: Test method starts with test [TestMethodPrefix]
|
||||
fun `test foo`() = Unit
|
||||
~~~~~~~~~~
|
||||
0 errors, 2 warnings
|
||||
""".trimIndent(),
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Autofix for src/Test.kt line 6: Remove prefix:
|
||||
@@ -6 +6
|
||||
- fun test_foo() = Unit
|
||||
+ fun foo() = Unit
|
||||
Autofix for src/Test.kt line 8: Remove prefix:
|
||||
@@ -8 +8
|
||||
- fun `test foo`() = Unit
|
||||
+ fun `foo`() = Unit
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detect format`() {
|
||||
lint().issues(FORMAT)
|
||||
.files(
|
||||
JUNIT_TEST_STUB,
|
||||
kotlin(
|
||||
"src/androidTest/com/example/Test.kt",
|
||||
"""
|
||||
import org.junit.Test
|
||||
class Test {
|
||||
@Test
|
||||
fun when_then() = Unit
|
||||
@Test
|
||||
fun given_when_then() = Unit
|
||||
|
||||
@Test
|
||||
fun foo() = Unit
|
||||
@Test
|
||||
fun foo_bar_baz_qux() = Unit
|
||||
@Test
|
||||
fun `foo bar baz`() = Unit
|
||||
}
|
||||
""",
|
||||
).indented(),
|
||||
)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/androidTest/com/example/Test.kt:9: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat]
|
||||
fun foo() = Unit
|
||||
~~~
|
||||
src/androidTest/com/example/Test.kt:11: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat]
|
||||
fun foo_bar_baz_qux() = Unit
|
||||
~~~~~~~~~~~~~~~
|
||||
src/androidTest/com/example/Test.kt:13: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat]
|
||||
fun `foo bar baz`() = Unit
|
||||
~~~~~~~~~~~~~
|
||||
0 errors, 3 warnings
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val JUNIT_TEST_STUB: TestFile = kotlin(
|
||||
"""
|
||||
package org.junit
|
||||
annotation class Test
|
||||
""",
|
||||
).indented()
|
||||
}
|
||||
}
|
Loading…
Reference in new issue