parent
cd4cd020b2
commit
eb0712d9b4
@ -0,0 +1,103 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.lintrules
|
||||
|
||||
import com.android.tools.lint.detector.api.*
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
class ContextCompatDetector : Detector(), SourceCodeScanner {
|
||||
|
||||
companion object {
|
||||
|
||||
const val FULLY_QUALIFIED_CONTEXT_COMPAT = "androidx.core.content.ContextCompat"
|
||||
|
||||
private val Implementation = Implementation(
|
||||
ContextCompatDetector::class.java,
|
||||
Scope.JAVA_FILE_SCOPE
|
||||
)
|
||||
|
||||
val ISSUE_GET_DRAWABLE_CALL = Issue.create(
|
||||
id = "UnsafeCompatGetDrawable",
|
||||
briefDescription = "Prohibits using the ContextCompat.getDrawable method",
|
||||
explanation = "Using this method can lead to crashes in older Android versions as newer features might not be available",
|
||||
category = Category.CORRECTNESS,
|
||||
severity = Severity.ERROR,
|
||||
implementation = Implementation
|
||||
)
|
||||
|
||||
val ISSUE_GET_COLOR_STATE_LIST_CALL = Issue.create(
|
||||
id = "UnsafeCompatGetColorStateList",
|
||||
briefDescription = "Prohibits using the ContextCompat.getColorStateList method",
|
||||
explanation = "Using this method can lead to crashes in older Android versions as newer features might not be available",
|
||||
category = Category.CORRECTNESS,
|
||||
severity = Severity.ERROR,
|
||||
implementation = Implementation
|
||||
)
|
||||
|
||||
val ISSUES = listOf(
|
||||
ISSUE_GET_DRAWABLE_CALL,
|
||||
ISSUE_GET_COLOR_STATE_LIST_CALL
|
||||
)
|
||||
}
|
||||
|
||||
override fun getApplicableMethodNames(): List<String>? = listOf(
|
||||
"getDrawable",
|
||||
"getColorStateList"
|
||||
)
|
||||
|
||||
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (!evaluator.isMemberInClass(method, FULLY_QUALIFIED_CONTEXT_COMPAT)) {
|
||||
return
|
||||
}
|
||||
|
||||
when (node.methodName) {
|
||||
"getDrawable" -> reportGetDrawableCall(context, node)
|
||||
"getColorStateList" -> reportGetColorStateListCall(context, node)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportGetDrawableCall(context: JavaContext, node: UCallExpression) = context.report(
|
||||
ISSUE_GET_DRAWABLE_CALL,
|
||||
context.getLocation(node),
|
||||
"This call can lead to crashes, replace with AppCompatResources.getDrawable",
|
||||
replaceUnsafeGetDrawableQuickFix(node)
|
||||
)
|
||||
|
||||
private fun reportGetColorStateListCall(context: JavaContext, node: UCallExpression) =
|
||||
context.report(
|
||||
ISSUE_GET_COLOR_STATE_LIST_CALL,
|
||||
context.getLocation(node),
|
||||
"This call can lead to crashes, replace with AppCompatResources.getColorStateList",
|
||||
replaceUnsafeGetColorStateListCallQuickFix(node)
|
||||
)
|
||||
|
||||
private fun replaceUnsafeGetDrawableQuickFix(node: UCallExpression): LintFix {
|
||||
val arguments = node.valueArguments.joinToString { it.asSourceString() }
|
||||
val newText = "AppCompatResources.getDrawable($arguments)"
|
||||
|
||||
return LintFix.create()
|
||||
.name("Replace with AppCompatResources.getDrawable")
|
||||
.replace()
|
||||
.all()
|
||||
.with(newText)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun replaceUnsafeGetColorStateListCallQuickFix(node: UCallExpression): LintFix {
|
||||
val arguments = node.valueArguments.joinToString { it.asSourceString() }
|
||||
val newText = "AppCompatResources.getColorStateList($arguments)"
|
||||
|
||||
return LintFix.create()
|
||||
.name("Replace with AppCompatResources.getColorStateList")
|
||||
.replace()
|
||||
.all()
|
||||
.with(newText)
|
||||
.build()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.lintrules
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
class ContextCompatDetectorTest : LintDetectorTest() {
|
||||
|
||||
override fun getDetector(): Detector = ContextCompatDetector()
|
||||
|
||||
override fun getIssues(): MutableList<Issue> = ContextCompatDetector.ISSUES.toMutableList()
|
||||
|
||||
@Test
|
||||
fun `report error when the ContextCompat getDrawable method is called`() {
|
||||
val code = """
|
||||
|package example
|
||||
|
|
||||
|import androidx.core.content.ContextCompat
|
||||
|import android.content.Context
|
||||
|
|
||||
|class Fake {
|
||||
|
|
||||
| fun trigger(context: Context, id: Int) {
|
||||
| ContextCompat.getDrawable(context, id)
|
||||
| }
|
||||
|
|
||||
|}""".trimMargin()
|
||||
|
||||
val expectedReport = """
|
||||
|src/example/Fake.kt:9: Error: This call can lead to crashes, replace with AppCompatResources.getDrawable [UnsafeCompatGetDrawable]
|
||||
| ContextCompat.getDrawable(context, id)
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|1 errors, 0 warnings""".trimMargin()
|
||||
|
||||
val expectedFixOutput = """
|
||||
|Fix for src/example/Fake.kt line 9: Replace with AppCompatResources.getDrawable:
|
||||
|@@ -9 +9
|
||||
|- ContextCompat.getDrawable(context, id)
|
||||
|+ AppCompatResources.getDrawable(context, id)""".trimMargin()
|
||||
|
||||
lint()
|
||||
.files(Context, ContextCompat, kotlin(code))
|
||||
.allowMissingSdk(true)
|
||||
.run()
|
||||
.expect(expectedReport)
|
||||
.expectFixDiffs(expectedFixOutput)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `report error when the ContextCompat getColorStateList method is called`() {
|
||||
val code = """
|
||||
|package example
|
||||
|
|
||||
|import androidx.core.content.ContextCompat
|
||||
|import android.content.Context
|
||||
|
|
||||
|class Fake {
|
||||
|
|
||||
| fun trigger(context: Context, id: Int) {
|
||||
| ContextCompat.getColorStateList(context, id)
|
||||
| }
|
||||
|
|
||||
|}""".trimMargin()
|
||||
|
||||
val expectedReport = """
|
||||
|src/example/Fake.kt:9: Error: This call can lead to crashes, replace with AppCompatResources.getColorStateList [UnsafeCompatGetColorStateList]
|
||||
| ContextCompat.getColorStateList(context, id)
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|1 errors, 0 warnings""".trimMargin()
|
||||
|
||||
val expectedFixOutput = """
|
||||
|Fix for src/example/Fake.kt line 9: Replace with AppCompatResources.getColorStateList:
|
||||
|@@ -9 +9
|
||||
|- ContextCompat.getColorStateList(context, id)
|
||||
|+ AppCompatResources.getColorStateList(context, id)""".trimMargin()
|
||||
|
||||
lint()
|
||||
.files(Context, ContextCompat, kotlin(code))
|
||||
.allowMissingSdk(true)
|
||||
.run()
|
||||
.expect(expectedReport)
|
||||
.expectFixDiffs(expectedFixOutput)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not report any errors if the getDrawable method being called is not from the ContextCompat class`() {
|
||||
val code = """
|
||||
|package example
|
||||
|
|
||||
|import android.content.Context
|
||||
|
|
||||
|class Fake {
|
||||
|
|
||||
| fun trigger(context: Context, id: Int) {
|
||||
| getDrawable(context, id)
|
||||
| }
|
||||
|
|
||||
| fun getDrawable(context: Context, id: Int) {}
|
||||
|
|
||||
|}""".trimMargin()
|
||||
|
||||
lint()
|
||||
.files(Context, kotlin(code))
|
||||
.allowMissingSdk(true)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not report any errors if the getColorStateList method being called is not from the ContextCompat class`() {
|
||||
val code = """
|
||||
|package example
|
||||
|
|
||||
|import android.content.Context
|
||||
|
|
||||
|class Fake {
|
||||
|
|
||||
| fun trigger(context: Context, id: Int) {
|
||||
| getColorStateList(context, id)
|
||||
| }
|
||||
|
|
||||
| fun getColorStateList(context: Context, id: Int) {}
|
||||
|
|
||||
|}""".trimMargin()
|
||||
|
||||
lint()
|
||||
.files(Context, kotlin(code))
|
||||
.allowMissingSdk(true)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
// As Lint doesn't have access to the real corresponding classes, we have to provide stubs
|
||||
companion object Stubs {
|
||||
|
||||
private val Context = java(
|
||||
"""
|
||||
|package android.content;
|
||||
|
|
||||
|public class Context {}
|
||||
|""".trimMargin()
|
||||
)
|
||||
|
||||
private val ContextCompat = java(
|
||||
"""
|
||||
|package androidx.core.content;
|
||||
|
|
||||
|public class ContextCompat {
|
||||
| public static void getDrawable(Context context, int resId) {}
|
||||
| public static void getColorStateList(Context context, int resId) {}
|
||||
|}
|
||||
|""".trimMargin()
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue