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