Source added
This commit is contained in:
parent
b2864b500e
commit
ba28ca859e
8352 changed files with 1487182 additions and 1 deletions
35
lintchecks/build.gradle.kts
Normal file
35
lintchecks/build.gradle.kts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
val signalJavaVersion: JavaVersion by rootProject.extra
|
||||
val signalKotlinJvmTarget: String by rootProject.extra
|
||||
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = signalJavaVersion
|
||||
targetCompatibility = signalJavaVersion
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion = JavaLanguageVersion.of(signalKotlinJvmTarget)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(lintLibs.lint.api)
|
||||
compileOnly(lintLibs.lint.checks)
|
||||
|
||||
testImplementation(lintLibs.lint.tests)
|
||||
testImplementation(lintLibs.lint.api)
|
||||
testImplementation(testLibs.junit.junit)
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes(
|
||||
"Lint-Registry-v2" to "org.signal.lint.Registry"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.WARNING
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
class AlertDialogBuilderDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableConstructorTypes(): List<String> {
|
||||
return listOf("android.app.AlertDialog.Builder", "androidx.appcompat.app.AlertDialog.Builder")
|
||||
}
|
||||
|
||||
override fun visitConstructor(context: JavaContext, node: UCallExpression, constructor: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(constructor, "android.app.AlertDialog.Builder")) {
|
||||
context.report(
|
||||
issue = ALERT_DIALOG_BUILDER_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'android.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder",
|
||||
quickfixData = quickFixIssueAlertDialogBuilder(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(constructor, "androidx.appcompat.app.AlertDialog.Builder")) {
|
||||
context.report(
|
||||
issue = ALERT_DIALOG_BUILDER_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'androidx.appcompat.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder",
|
||||
quickfixData = quickFixIssueAlertDialogBuilder(node)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun quickFixIssueAlertDialogBuilder(alertBuilderCall: UCallExpression): LintFix {
|
||||
val arguments = alertBuilderCall.valueArguments
|
||||
val context = arguments[0]
|
||||
|
||||
var fixSource = "new com.google.android.material.dialog.MaterialAlertDialogBuilder"
|
||||
|
||||
when (arguments.size) {
|
||||
1 -> fixSource += String.format("(%s)", context)
|
||||
2 -> {
|
||||
val themeOverride = arguments[1]
|
||||
fixSource += String.format("(%s, %s)", context, themeOverride)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments")
|
||||
}
|
||||
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(alertBuilderCall.asSourceString())
|
||||
.shortenNames()
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ALERT_DIALOG_BUILDER_USAGE: Issue = Issue.create(
|
||||
id = "AlertDialogBuilderUsage",
|
||||
briefDescription = "Creating dialog with AlertDialog.Builder instead of MaterialAlertDialogBuilder",
|
||||
explanation = "Signal utilizes MaterialAlertDialogBuilder for more consistent and pleasant AlertDialogs.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = WARNING,
|
||||
implementation = Implementation(AlertDialogBuilderDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.WARNING
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
/**
|
||||
* Detects usages of Rx observable stream's blockingGet method. This is considered harmful, as
|
||||
* blockingGet will take any error it emits and throw it as a runtime error. The alternative options
|
||||
* are to:
|
||||
*
|
||||
* 1. Provide a synchronous method instead of relying on an observable method.
|
||||
* 2. Pass the observable to the caller to allow them to wait on it via a flatMap or other operator.
|
||||
* 3. Utilize safeBlockingGet, which will bubble up the interrupted exception.
|
||||
*
|
||||
* Note that (1) is the most preferred route here.
|
||||
*/
|
||||
class BlockingGetDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableMethodNames(): List<String> {
|
||||
return listOf("blockingGet")
|
||||
}
|
||||
|
||||
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(method, "io.reactivex.rxjava3.core.Single")) {
|
||||
context.report(
|
||||
issue = UNSAFE_BLOCKING_GET,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'Single#blockingGet' instead of 'RxExtensions.safeBlockingGet'",
|
||||
quickfixData = null
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "io.reactivex.rxjava3.core.Observable")) {
|
||||
context.report(
|
||||
issue = UNSAFE_BLOCKING_GET,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'Observable#blockingGet' instead of 'RxExtensions.safeBlockingGet'",
|
||||
quickfixData = null
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "io.reactivex.rxjava3.core.Flowable")) {
|
||||
context.report(
|
||||
issue = UNSAFE_BLOCKING_GET,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'Flowable#blockingGet' instead of 'RxExtensions.safeBlockingGet'",
|
||||
quickfixData = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UNSAFE_BLOCKING_GET: Issue = Issue.create(
|
||||
id = "UnsafeBlockingGet",
|
||||
briefDescription = "BlockingGet is considered unsafe and should be avoided.",
|
||||
explanation = "Prefer exposing the Observable instead. If you need to block, use RxExtensions.safeBlockingGet",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = WARNING,
|
||||
implementation = Implementation(BlockingGetDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
81
lintchecks/src/main/java/org/signal/lint/CardViewDetector.kt
Normal file
81
lintchecks/src/main/java/org/signal/lint/CardViewDetector.kt
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.WARNING
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
class CardViewDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableConstructorTypes(): List<String> {
|
||||
return listOf("androidx.cardview.widget.CardView")
|
||||
}
|
||||
|
||||
override fun visitConstructor(context: JavaContext, node: UCallExpression, constructor: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(constructor, "androidx.cardview.widget.CardView")) {
|
||||
context.report(
|
||||
issue = CARD_VIEW_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView",
|
||||
quickfixData = quickFixIssueAlertDialogBuilder(node)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun quickFixIssueAlertDialogBuilder(alertBuilderCall: UCallExpression): LintFix {
|
||||
val arguments = alertBuilderCall.valueArguments
|
||||
val context = arguments[0]
|
||||
|
||||
var fixSource = "new com.google.android.material.card.MaterialCardView"
|
||||
|
||||
//Context context, AttributeSet attrs, int defStyleAttr
|
||||
when (arguments.size) {
|
||||
1 -> fixSource += String.format("(%s)", context)
|
||||
2 -> {
|
||||
val attrs = arguments[1]
|
||||
fixSource += String.format("(%s, %s)", context, attrs)
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val attributes = arguments[1]
|
||||
val defStyleAttr = arguments[2]
|
||||
fixSource += String.format("(%s, %s, %s)", context, attributes, defStyleAttr)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments")
|
||||
}
|
||||
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(alertBuilderCall.asSourceString())
|
||||
.shortenNames()
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CARD_VIEW_USAGE: Issue = Issue.create(
|
||||
id = "CardViewUsage",
|
||||
briefDescription = "Utilizing CardView instead of MaterialCardView subclass",
|
||||
explanation = "Signal utilizes MaterialCardView for more consistent and pleasant CardViews.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = WARNING,
|
||||
implementation = Implementation(CardViewDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.android.tools.lint.detector.api.SourceCodeScanner
|
||||
import org.jetbrains.uast.UClass
|
||||
import java.util.Locale
|
||||
|
||||
class RecipientIdDatabaseDetector : Detector(), SourceCodeScanner {
|
||||
override fun getApplicableUastTypes(): List<Class<UClass>> {
|
||||
return listOf(UClass::class.java)
|
||||
}
|
||||
|
||||
override fun createUastHandler(context: JavaContext): UElementHandler {
|
||||
return object : UElementHandler() {
|
||||
override fun visitClass(node: UClass) {
|
||||
if (node.qualifiedName == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.extendsList == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (EXEMPTED_CLASSES.contains(node.qualifiedName)) {
|
||||
return
|
||||
}
|
||||
|
||||
val doesNotExtendDatabase = node.extendsList?.referencedTypes.orEmpty().none { it.className == "Database" }
|
||||
if (doesNotExtendDatabase) {
|
||||
return
|
||||
}
|
||||
|
||||
val implementsReference = node.interfaces.any { it.qualifiedName == "org.thoughtcrime.securesms.database.RecipientIdDatabaseReference" }
|
||||
if (implementsReference) {
|
||||
return
|
||||
}
|
||||
|
||||
val recipientFields = node.allFields
|
||||
.filter { it.type.equalsToText("java.lang.String") }
|
||||
.filter { it.name.lowercase(Locale.getDefault()).contains("recipient") }
|
||||
|
||||
for (field in recipientFields) {
|
||||
context.report(
|
||||
issue = RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
|
||||
scope = field,
|
||||
location = context.getLocation(field),
|
||||
message = "If you reference a RecipientId in your table, you must implement the RecipientIdDatabaseReference interface.",
|
||||
quickfixData = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val RECIPIENT_ID_DATABASE_REFERENCE_ISSUE: Issue = Issue.create(
|
||||
id = "RecipientIdDatabaseReferenceUsage",
|
||||
briefDescription = "Referencing a RecipientId in a database without implementing RecipientIdDatabaseReference.",
|
||||
explanation = "If you reference a RecipientId in a column, you need to be able to handle the remapping of one RecipientId to another, which RecipientIdDatabaseReference enforces.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(RecipientIdDatabaseDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
private val EXEMPTED_CLASSES = setOf("org.thoughtcrime.securesms.database.RecipientDatabase")
|
||||
}
|
||||
}
|
||||
31
lintchecks/src/main/java/org/signal/lint/Registry.kt
Normal file
31
lintchecks/src/main/java/org/signal/lint/Registry.kt
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.client.api.IssueRegistry
|
||||
import com.android.tools.lint.client.api.Vendor
|
||||
import com.android.tools.lint.detector.api.CURRENT_API
|
||||
|
||||
class Registry : IssueRegistry() {
|
||||
override val vendor = Vendor(
|
||||
vendorName = "Signal",
|
||||
identifier = "Signal",
|
||||
feedbackUrl = "Signal",
|
||||
contact = "Signal"
|
||||
)
|
||||
|
||||
override val issues = listOf(
|
||||
SignalLogDetector.LOG_NOT_SIGNAL,
|
||||
SignalLogDetector.LOG_NOT_APP,
|
||||
SignalLogDetector.INLINE_TAG,
|
||||
VersionCodeDetector.VERSION_CODE_USAGE,
|
||||
AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE,
|
||||
BlockingGetDetector.UNSAFE_BLOCKING_GET,
|
||||
RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
|
||||
ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE,
|
||||
StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE,
|
||||
CardViewDetector.CARD_VIEW_USAGE,
|
||||
SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE,
|
||||
SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE
|
||||
)
|
||||
|
||||
override val api = CURRENT_API
|
||||
}
|
||||
145
lintchecks/src/main/java/org/signal/lint/SignalLogDetector.kt
Normal file
145
lintchecks/src/main/java/org/signal/lint/SignalLogDetector.kt
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.java.JavaUSimpleNameReferenceExpression
|
||||
import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression
|
||||
import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression
|
||||
|
||||
class SignalLogDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableMethodNames(): List<String> {
|
||||
return listOf("v", "d", "i", "w", "e", "wtf")
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(method, "android.util.Log")) {
|
||||
context.report(
|
||||
issue = LOG_NOT_SIGNAL,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'android.util.Log' instead of a Signal Logger",
|
||||
quickfixData = quickFixIssueLog(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "org.signal.glide.Log")) {
|
||||
context.report(
|
||||
issue = LOG_NOT_SIGNAL,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'org.signal.glide.Log' instead of a Signal Logger",
|
||||
quickfixData = quickFixIssueLog(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "org.signal.libsignal.protocol.logging.Log")) {
|
||||
context.report(
|
||||
issue = LOG_NOT_APP,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using Signal server logger instead of app level Logger",
|
||||
quickfixData = quickFixIssueLog(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "org.signal.core.util.logging.Log")) {
|
||||
val arguments = node.valueArguments
|
||||
val tag = arguments[0]
|
||||
|
||||
val invalidTagType = setOf(
|
||||
JavaUSimpleNameReferenceExpression::class,
|
||||
KotlinUSimpleReferenceExpression::class,
|
||||
KotlinUQualifiedReferenceExpression::class
|
||||
).none { it.isInstance(tag) }
|
||||
|
||||
if (invalidTagType) {
|
||||
context.report(
|
||||
issue = INLINE_TAG,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Not using a tag constant"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun quickFixIssueLog(logCall: UCallExpression): LintFix {
|
||||
val arguments = logCall.valueArguments
|
||||
val methodName = logCall.methodName
|
||||
val tag = arguments[0]
|
||||
|
||||
var fixSource = "org.signal.core.util.logging.Log."
|
||||
|
||||
when (arguments.size) {
|
||||
2 -> {
|
||||
val msgOrThrowable = arguments[1]
|
||||
fixSource += String.format("%s(%s, %s)", methodName, tag, msgOrThrowable.asSourceString())
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val msg = arguments[1]
|
||||
val throwable = arguments[2]
|
||||
fixSource += String.format("%s(%s, %s, %s)", methodName, tag, msg.asSourceString(), throwable.asSourceString())
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Log overloads should have 2 or 3 arguments")
|
||||
}
|
||||
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(logCall.asSourceString())
|
||||
.shortenNames()
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val LOG_NOT_SIGNAL: Issue = Issue.create(
|
||||
id = "LogNotSignal",
|
||||
briefDescription = "Logging call to Android Log instead of Signal's Logger",
|
||||
explanation = "Signal has its own logger which must be used.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SignalLogDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
val LOG_NOT_APP: Issue = Issue.create(
|
||||
id = "LogNotAppSignal",
|
||||
briefDescription = "Logging call to Signal Service Log instead of App level Logger",
|
||||
explanation = "Signal app layer has its own logger which must be used.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SignalLogDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
val INLINE_TAG: Issue = Issue.create(
|
||||
id = "LogTagInlined",
|
||||
briefDescription = "Use of an inline string in a TAG",
|
||||
explanation = "Often a sign of left in temporary log statements, always use a tag constant.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SignalLogDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
class StartForegroundServiceDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableMethodNames(): List<String> {
|
||||
return listOf("startForegroundService")
|
||||
}
|
||||
|
||||
override fun visitMethodCall(context: JavaContext, call: UCallExpression, method: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
val classes = context.uastFile?.classes.orEmpty()
|
||||
val isForegroundServiceUtil = classes.any { it.name == "ForegroundServiceUtil" }
|
||||
if (isForegroundServiceUtil) {
|
||||
return
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "androidx.core.content.ContextCompat")) {
|
||||
context.report(
|
||||
issue = START_FOREGROUND_SERVICE_ISSUE,
|
||||
scope = call,
|
||||
location = context.getLocation(call),
|
||||
message = "Using 'ContextCompat.startForegroundService' instead of a ForegroundServiceUtil"
|
||||
)
|
||||
} else if (evaluator.isMemberInClass(method, "android.content.Context")) {
|
||||
context.report(
|
||||
issue = START_FOREGROUND_SERVICE_ISSUE,
|
||||
scope = call,
|
||||
location = context.getLocation(call),
|
||||
message = "Using 'Context.startForegroundService' instead of a ForegroundServiceUtil"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val START_FOREGROUND_SERVICE_ISSUE: Issue = Issue.create(
|
||||
id = "StartForegroundServiceUsage",
|
||||
briefDescription = "Starting a foreground service using ContextCompat.startForegroundService instead of ForegroundServiceUtil",
|
||||
explanation = "Starting a foreground service may fail, and we should prefer our utils to make sure they're started correctly",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(StartForegroundServiceDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.UExpression
|
||||
import org.jetbrains.uast.UQualifiedReferenceExpression
|
||||
|
||||
/**
|
||||
* Lint detector that flags usage of System.out.println and kotlin.io.println methods.
|
||||
*/
|
||||
class SystemOutPrintLnDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
override fun getApplicableMethodNames(): List<String> {
|
||||
return listOf("println", "print")
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(method, "java.io.PrintStream")) {
|
||||
if (isSystemOutCall(node.receiver)) {
|
||||
context.report(
|
||||
issue = SYSTEM_OUT_PRINTLN_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'System.out.${method.name}' instead of Signal Logger",
|
||||
quickfixData = createQuickFix(node)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for kotlin.io.println (top-level function)
|
||||
if (method.name == "println" && evaluator.isMemberInClass(method, "kotlin.io.ConsoleKt")) {
|
||||
context.report(
|
||||
issue = KOTLIN_IO_PRINTLN_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'kotlin.io.println' instead of Signal Logger.",
|
||||
quickfixData = createQuickFix(node)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSystemOutCall(receiver: UExpression?): Boolean {
|
||||
return receiver is UQualifiedReferenceExpression &&
|
||||
receiver.selector.asRenderString() == "out" &&
|
||||
receiver.receiver.asRenderString().endsWith("System")
|
||||
}
|
||||
|
||||
private fun createQuickFix(node: UCallExpression): LintFix {
|
||||
val arguments = node.valueArguments
|
||||
val message = if (arguments.isNotEmpty()) arguments[0].asSourceString() else "\"\""
|
||||
|
||||
val fixSource = "org.signal.core.util.logging.Log.d(TAG, $message)"
|
||||
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(node.sourcePsi?.text)
|
||||
.shortenNames()
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SYSTEM_OUT_PRINTLN_USAGE: Issue = Issue.create(
|
||||
id = "SystemOutPrintLnUsage",
|
||||
briefDescription = "Usage of System.out.println/print",
|
||||
explanation = "System.out.println/print should not be used in production code. Use Signal Logger instead.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SystemOutPrintLnDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
val KOTLIN_IO_PRINTLN_USAGE: Issue = Issue.create(
|
||||
id = "KotlinIOPrintLnUsage",
|
||||
briefDescription = "Usage of kotlin.io.println",
|
||||
explanation = "kotlin.io.println should not be used in production code. Use proper logging instead.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SystemOutPrintLnDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
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.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.android.tools.lint.detector.api.SourceCodeScanner
|
||||
import org.jetbrains.uast.UClass
|
||||
import java.util.Locale
|
||||
|
||||
class ThreadIdDatabaseDetector : Detector(), SourceCodeScanner {
|
||||
override fun getApplicableUastTypes(): List<Class<UClass>> {
|
||||
return listOf(UClass::class.java)
|
||||
}
|
||||
|
||||
override fun createUastHandler(context: JavaContext): UElementHandler {
|
||||
return object : UElementHandler() {
|
||||
override fun visitClass(node: UClass) {
|
||||
if (node.qualifiedName == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (node.extendsList == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (EXEMPTED_CLASSES.contains(node.qualifiedName)) {
|
||||
return
|
||||
}
|
||||
|
||||
val referencedTypes = node.extendsList?.referencedTypes.orEmpty()
|
||||
val doesNotExtendDatabase = referencedTypes.none { classType -> "Database" == classType.className }
|
||||
if (doesNotExtendDatabase) {
|
||||
return
|
||||
}
|
||||
|
||||
val implementsReference = node.interfaces.any { nodeInterface ->
|
||||
"org.thoughtcrime.securesms.database.ThreadIdDatabaseReference" == nodeInterface.qualifiedName
|
||||
}
|
||||
if (implementsReference) {
|
||||
return
|
||||
}
|
||||
|
||||
val recipientFields = node.allFields
|
||||
.filter { field -> field.type.equalsToText("java.lang.String") }
|
||||
.filter { field -> field.name.lowercase(Locale.getDefault()).contains("thread") }
|
||||
|
||||
for (field in recipientFields) {
|
||||
context.report(
|
||||
issue = THREAD_ID_DATABASE_REFERENCE_ISSUE,
|
||||
scope = field,
|
||||
location = context.getLocation(field),
|
||||
message = "If you reference a thread ID in your table, you must implement the ThreadIdDatabaseReference interface.",
|
||||
quickfixData = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val THREAD_ID_DATABASE_REFERENCE_ISSUE: Issue = Issue.create(
|
||||
id = "ThreadIdDatabaseReferenceUsage",
|
||||
briefDescription = "Referencing a thread ID in a database without implementing ThreadIdDatabaseReference.",
|
||||
explanation = "If you reference a thread ID in a column, you need to be able to handle the remapping of one thread ID to another, which ThreadIdDatabaseReference enforces.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(ThreadIdDatabaseDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
private val EXEMPTED_CLASSES = setOf("org.thoughtcrime.securesms.database.ThreadDatabase")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.client.api.UElementHandler
|
||||
import com.android.tools.lint.detector.api.Category.Companion.CORRECTNESS
|
||||
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.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.WARNING
|
||||
import com.intellij.psi.PsiTypes
|
||||
import org.jetbrains.uast.UExpression
|
||||
|
||||
class VersionCodeDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableUastTypes(): List<Class<UExpression>> {
|
||||
return listOf(UExpression::class.java)
|
||||
}
|
||||
|
||||
override fun createUastHandler(context: JavaContext): UElementHandler {
|
||||
return ExpressionChecker(context)
|
||||
}
|
||||
|
||||
private inner class ExpressionChecker(private val context: JavaContext) : UElementHandler() {
|
||||
private val evaluator = context.evaluator
|
||||
private val versionCodeClass = evaluator.findClass("android.os.Build.VERSION_CODES")
|
||||
|
||||
override fun visitExpression(node: UExpression) {
|
||||
if (versionCodeClass != null && node.getExpressionType() === PsiTypes.intType()) {
|
||||
val javaPsi = node.javaPsi
|
||||
|
||||
if (javaPsi != null) {
|
||||
val resolved = evaluator.resolve(javaPsi)
|
||||
|
||||
if (resolved != null && resolved.parent == versionCodeClass) {
|
||||
val evaluated = node.evaluate()
|
||||
|
||||
if (evaluated != null) {
|
||||
context.report(
|
||||
issue = VERSION_CODE_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'VERSION_CODES' reference instead of the numeric value $evaluated",
|
||||
quickfixData = quickFixIssueInlineValue(node, evaluated.toString())
|
||||
)
|
||||
} else {
|
||||
context.report(
|
||||
issue = VERSION_CODE_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'VERSION_CODES' reference instead of the numeric value",
|
||||
quickfixData = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun quickFixIssueInlineValue(node: UExpression, fixSource: String): LintFix {
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(node.asSourceString())
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val VERSION_CODE_USAGE: Issue = Issue.create(
|
||||
id = "VersionCodeUsage",
|
||||
briefDescription = "Using 'VERSION_CODES' reference instead of the numeric value",
|
||||
explanation = "Signal style is to use the numeric value.",
|
||||
category = CORRECTNESS,
|
||||
priority = 5,
|
||||
severity = WARNING,
|
||||
implementation = Implementation(VersionCodeDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class AlertDialogBuilderDetectorTest {
|
||||
@Test
|
||||
fun androidAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.app.AlertDialog;
|
||||
public class Example {
|
||||
public void buildDialog() {
|
||||
new AlertDialog.Builder(context).show();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'android.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder [AlertDialogBuilderUsage]
|
||||
new AlertDialog.Builder(context).show();
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):
|
||||
@@ -5 +5
|
||||
- new AlertDialog.Builder(context).show();
|
||||
+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context).show();
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun androidAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_2_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.app.AlertDialog;
|
||||
public class Example {
|
||||
public void buildDialog() {
|
||||
new AlertDialog.Builder(context, themeOverride).show();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'android.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder [AlertDialogBuilderUsage]
|
||||
new AlertDialog.Builder(context, themeOverride).show();
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride):
|
||||
@@ -5 +5
|
||||
- new AlertDialog.Builder(context, themeOverride).show();
|
||||
+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride).show();
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun androidAlertDialogBuilderUsed_withAssignment_LogAlertDialogBuilderUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.app.AlertDialog;
|
||||
public class Example {
|
||||
public void buildDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'android.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder [AlertDialogBuilderUsage]
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):
|
||||
@@ -5 +5
|
||||
- AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
+ AlertDialog.Builder builder = new com.google.android.material.dialog.MaterialAlertDialogBuilder(context)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appcompatAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appCompatAlertDialogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
public class Example {
|
||||
public void buildDialog() {
|
||||
new AlertDialog.Builder(context).show();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.appcompat.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder [AlertDialogBuilderUsage]
|
||||
new AlertDialog.Builder(context).show();
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):
|
||||
@@ -5 +5
|
||||
- new AlertDialog.Builder(context).show();
|
||||
+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context).show();
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appcompatAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_2_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appCompatAlertDialogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
public class Example {
|
||||
public void buildDialog() {
|
||||
new AlertDialog.Builder(context, themeOverride).show();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.appcompat.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder [AlertDialogBuilderUsage]
|
||||
new AlertDialog.Builder(context, themeOverride).show();
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride):
|
||||
@@ -5 +5
|
||||
- new AlertDialog.Builder(context, themeOverride).show();
|
||||
+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride).show();
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appcompatAlertDialogBuilderUsed_withAssignment_LogAlertDialogBuilderUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appCompatAlertDialogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
public class Example {
|
||||
public void buildDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.appcompat.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder [AlertDialogBuilderUsage]
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):
|
||||
@@ -5 +5
|
||||
- AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
+ AlertDialog.Builder builder = new com.google.android.material.dialog.MaterialAlertDialogBuilder(context)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val appCompatAlertDialogStub = kotlin(readResourceAsString("AppCompatAlertDialogStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
135
lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.kt
Normal file
135
lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.kt
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class CardViewDetectorTest {
|
||||
@Test
|
||||
fun cardViewUsed_LogCardViewUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
cardViewStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.cardview.widget.CardView;
|
||||
public class Example {
|
||||
public void buildCardView() {
|
||||
new CardView(context);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(CardViewDetector.CARD_VIEW_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]
|
||||
new CardView(context);
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):
|
||||
@@ -5 +5
|
||||
- new CardView(context);
|
||||
+ new com.google.android.material.card.MaterialCardView(context);
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cardViewUsed_LogCardViewUsage_2_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
cardViewStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.cardview.widget.CardView;
|
||||
public class Example {
|
||||
public void buildCardView() {
|
||||
new CardView(context, attrs);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(CardViewDetector.CARD_VIEW_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]
|
||||
new CardView(context, attrs);
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context, attrs):
|
||||
@@ -5 +5
|
||||
- new CardView(context, attrs);
|
||||
+ new com.google.android.material.card.MaterialCardView(context, attrs);
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cardViewUsed_withAssignment_LogCardViewUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
cardViewStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.cardview.widget.CardView;
|
||||
public class Example {
|
||||
public void buildCardView() {
|
||||
CardView cardView = new CardView(context)
|
||||
;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(CardViewDetector.CARD_VIEW_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]
|
||||
CardView cardView = new CardView(context)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):
|
||||
@@ -5 +5
|
||||
- CardView cardView = new CardView(context)
|
||||
+ CardView cardView = new com.google.android.material.card.MaterialCardView(context)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val cardViewStub = kotlin(readResourceAsString("CardViewStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class RecipientIdDatabaseDetectorTest {
|
||||
@Test
|
||||
fun recipientIdDatabase_databaseHasRecipientFieldButDoesNotImplementInterface_showError() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
public class Example extends Database {
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:3: Error: If you reference a RecipientId in your table, you must implement the RecipientIdDatabaseReference interface. [RecipientIdDatabaseReferenceUsage]
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recipientIdDatabase_databaseHasRecipientFieldAndImplementsInterface_noError() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
recipientReferenceStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.thoughtcrime.securesms.database.RecipientIdDatabaseReference;
|
||||
public class Example extends Database implements RecipientIdDatabaseReference {
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
@Override
|
||||
public void remapRecipient(RecipientId fromId, RecipientId toId) {}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val recipientReferenceStub = kotlin(readResourceAsString("RecipientIdDatabaseReferenceStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import com.android.tools.lint.checks.infrastructure.TestMode
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class SignalLogDetectorTest {
|
||||
@Test
|
||||
fun androidLogUsed_LogNotSignal_2_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.util.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'android.util.Log' instead of a Signal Logger [LogNotSignal]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d("TAG", "msg"):
|
||||
@@ -5 +5
|
||||
- Log.d("TAG", "msg");
|
||||
+ org.signal.core.util.logging.Log.d("TAG", "msg");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun androidLogUsed_LogNotSignal_3_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.util.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'android.util.Log' instead of a Signal Logger [LogNotSignal]
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.w("TAG", "msg", new Exception()):
|
||||
@@ -5 +5
|
||||
- Log.w("TAG", "msg", new Exception());
|
||||
+ org.signal.core.util.logging.Log.w("TAG", "msg", new Exception());
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signalServiceLogUsed_LogNotApp_2_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
serviceLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_APP)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using Signal server logger instead of app level Logger [LogNotAppSignal]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d("TAG", "msg"):
|
||||
@@ -5 +5
|
||||
- Log.d("TAG", "msg");
|
||||
+ org.signal.core.util.logging.Log.d("TAG", "msg");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signalServiceLogUsed_LogNotApp_3_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
serviceLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_APP)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using Signal server logger instead of app level Logger [LogNotAppSignal]
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.w("TAG", "msg", new Exception()):
|
||||
@@ -5 +5
|
||||
- Log.w("TAG", "msg", new Exception());
|
||||
+ org.signal.core.util.logging.Log.w("TAG", "msg", new Exception());
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_tag_constant() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.core.util.logging.Log;
|
||||
public class Example {
|
||||
private static final String TAG = Log.tag(Example.class);
|
||||
public void log() {
|
||||
Log.d(TAG, "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_tag_constant_kotlin() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import org.signal.core.util.logging.Log
|
||||
class Example {
|
||||
const val TAG: String = Log.tag(Example::class.java)
|
||||
fun log() {
|
||||
Log.d(TAG, "msg")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.skipTestModes(TestMode.REORDER_ARGUMENTS)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_tag_companion_kotlin() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import org.signal.core.util.logging.Log
|
||||
class Example {
|
||||
companion object { val TAG: String = Log.tag(Example::class.java) }
|
||||
fun log() {
|
||||
Log.d(TAG, "msg")
|
||||
}
|
||||
}
|
||||
fun logOutsie() {
|
||||
Log.d(Example.TAG, "msg")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.skipTestModes(TestMode.REORDER_ARGUMENTS)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_inline_tag() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.core.util.logging.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Not using a tag constant [LogTagInlined]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_inline_tag_kotlin() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import org.signal.core.util.logging.Log
|
||||
class Example {
|
||||
fun log() {
|
||||
Log.d("TAG", "msg")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.kt:5: Error: Not using a tag constant [LogTagInlined]
|
||||
Log.d("TAG", "msg")
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glideLogUsed_LogNotSignal_2_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
glideLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.glide.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'org.signal.glide.Log' instead of a Signal Logger [LogNotSignal]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d("TAG", "msg"):
|
||||
@@ -5 +5
|
||||
- Log.d("TAG", "msg");
|
||||
+ org.signal.core.util.logging.Log.d("TAG", "msg");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val serviceLogStub = kotlin(readResourceAsString("ServiceLogStub.kt"))
|
||||
private val appLogStub = kotlin(readResourceAsString("AppLogStub.kt"))
|
||||
private val glideLogStub = kotlin(readResourceAsString("GlideLogStub.kt"))
|
||||
|
||||
private fun readResourceAsString(resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class StartForegroundServiceDetectorTest {
|
||||
@Test
|
||||
fun contextCompatUsed() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
contextCompatStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.core.content.ContextCompat;
|
||||
public class Example {
|
||||
public void start() {
|
||||
ContextCompat.startForegroundService(context, new Intent());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.allowMissingSdk()
|
||||
.issues(StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'ContextCompat.startForegroundService' instead of a ForegroundServiceUtil [StartForegroundServiceUsage]
|
||||
ContextCompat.startForegroundService(context, new Intent());
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextUsed() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
contextStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.content.Context;
|
||||
public class Example {
|
||||
Context context;
|
||||
public void start() {
|
||||
context.startForegroundService(new Intent());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.allowMissingSdk()
|
||||
.issues(StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:6: Error: Using 'Context.startForegroundService' instead of a ForegroundServiceUtil [StartForegroundServiceUsage]
|
||||
context.startForegroundService(new Intent());
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private val contextCompatStub = kotlin(readResourceAsString("ContextCompatStub.kt"))
|
||||
private val contextStub = kotlin(readResourceAsString("ContextStub.kt"))
|
||||
|
||||
private fun readResourceAsString(resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class SystemOutPrintLnDetectorTest {
|
||||
|
||||
@Test
|
||||
fun systemOutPrintlnUsed_Java() {
|
||||
TestLintTask.lint()
|
||||
.allowMissingSdk()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
public class Example {
|
||||
public void log() {
|
||||
System.out.println("Hello World");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:4: Error: Using 'System.out.println' instead of proper logging [SystemOutPrintLnUsage]
|
||||
System.out.println("Hello World");
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 4: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello World"):
|
||||
@@ -4 +4
|
||||
- System.out.println("Hello World");
|
||||
+ org.signal.core.util.logging.Log.d(TAG, "Hello World");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun systemOutPrintUsed_Java() {
|
||||
TestLintTask.lint()
|
||||
.allowMissingSdk()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
public class Example {
|
||||
public void log() {
|
||||
System.out.print("Hello");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:4: Error: Using 'System.out.print' instead of proper logging [SystemOutPrintLnUsage]
|
||||
System.out.print("Hello");
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 4: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello"):
|
||||
@@ -4 +4
|
||||
- System.out.print("Hello");
|
||||
+ org.signal.core.util.logging.Log.d(TAG, "Hello");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun kotlinIOPrintlnUsed_Kotlin() {
|
||||
TestLintTask.lint()
|
||||
.allowMissingSdk()
|
||||
.files(
|
||||
kotlinIOStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import kotlin.io.println
|
||||
class Example {
|
||||
fun log() {
|
||||
println("Hello World")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.kt:5: Error: Using 'kotlin.io.println' instead of proper logging [KotlinIOPrintLnUsage]
|
||||
println("Hello World")
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.kt line 5: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello World"):
|
||||
@@ -5 +5
|
||||
- println("Hello World")
|
||||
+ org.signal.core.util.logging.Log.d(TAG, "Hello World")
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun kotlinIOPrintlnUsed_TopLevel_Kotlin() {
|
||||
TestLintTask.lint()
|
||||
.allowMissingSdk()
|
||||
.files(
|
||||
kotlinIOStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
fun example() {
|
||||
println("Hello World")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/test.kt:3: Error: Using 'kotlin.io.println' instead of proper logging [KotlinIOPrintLnUsage]
|
||||
println("Hello World")
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/test.kt line 3: Replace with org.signal.core.util.logging.Log.d(TAG, "Hello World"):
|
||||
@@ -3 +3
|
||||
- println("Hello World")
|
||||
+ org.signal.core.util.logging.Log.d(TAG, "Hello World")
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun systemOutPrintlnWithNoArgs_Java() {
|
||||
TestLintTask.lint()
|
||||
.allowMissingSdk()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
public class Example {
|
||||
public void log() {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:4: Error: Using 'System.out.println' instead of proper logging [SystemOutPrintLnUsage]
|
||||
System.out.println();
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 4: Replace with org.signal.core.util.logging.Log.d(TAG, ""):
|
||||
@@ -4 +4
|
||||
- System.out.println();
|
||||
+ org.signal.core.util.logging.Log.d(TAG, "");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun regularPrintStreamMethodsNotFlagged() {
|
||||
TestLintTask.lint()
|
||||
.allowMissingSdk()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import java.io.PrintStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
public class Example {
|
||||
public void log() {
|
||||
PrintStream ps = new PrintStream(new ByteArrayOutputStream());
|
||||
ps.println("This should not be flagged");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(
|
||||
SystemOutPrintLnDetector.SYSTEM_OUT_PRINTLN_USAGE,
|
||||
SystemOutPrintLnDetector.KOTLIN_IO_PRINTLN_USAGE
|
||||
)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val kotlinIOStub = kotlin(readResourceAsString("KotlinIOStub.kt"))
|
||||
|
||||
private fun readResourceAsString(resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class ThreadIdDatabaseDetectorTest {
|
||||
@Test
|
||||
fun threadIdDatabase_databaseHasThreadFieldButDoesNotImplementInterface_showError() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
public class Example extends Database {
|
||||
private static final String THREAD_ID = "thread_id";
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:3: Error: If you reference a thread ID in your table, you must implement the ThreadIdDatabaseReference interface. [ThreadIdDatabaseReferenceUsage]
|
||||
private static final String THREAD_ID = "thread_id";
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun threadIdDatabase_databaseHasThreadFieldAndImplementsInterface_noError() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
threadReferenceStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.thoughtcrime.securesms.database.ThreadIdDatabaseReference;
|
||||
public class Example extends Database implements ThreadIdDatabaseReference {
|
||||
private static final String THREAD_ID = "thread_id";
|
||||
@Override
|
||||
public void remapThread(long fromId, long toId) {}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val threadReferenceStub = kotlin(readResourceAsString("ThreadIdDatabaseReferenceStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class VersionCodeDetectorTest {
|
||||
@Test
|
||||
fun version_code_constant_referenced_in_code() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.os.Build;
|
||||
public class Example {
|
||||
public void versionCodeMention() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'VERSION_CODES' reference instead of the numeric value 21 [VersionCodeUsage]
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with 21:
|
||||
@@ -5 +5
|
||||
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
+ if (Build.VERSION.SDK_INT >= 21) {
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun numeric_value_referenced_in_code() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.os.Build;
|
||||
public class Example {
|
||||
public void versionCodeMention() {
|
||||
if (Build.VERSION.SDK_INT >= 22) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun non_version_code_constant_referenced_in_code() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.os.Build;
|
||||
public class Example {
|
||||
private final static int LOLLIPOP = 21;
|
||||
public void versionCodeMention() {
|
||||
if (Build.VERSION.SDK_INT >= LOLLIPOP) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun version_code_constant_referenced_in_TargetApi_attribute_and_inner_class_import() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.annotation.TargetApi;
|
||||
public class Example {
|
||||
@TargetApi(VERSION_CODES.N)
|
||||
public void versionCodeMention() {
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'VERSION_CODES' reference instead of the numeric value 24 [VersionCodeUsage]
|
||||
@TargetApi(VERSION_CODES.N)
|
||||
~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with 24:
|
||||
@@ -5 +5
|
||||
- @TargetApi(VERSION_CODES.N)
|
||||
+ @TargetApi(24)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun version_code_constant_referenced_in_RequiresApi_attribute_with_named_parameter() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
requiresApiStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.os.Build;
|
||||
import android.annotation.RequiresApi;
|
||||
public class Example {
|
||||
@RequiresApi(app = Build.VERSION_CODES.M)
|
||||
public void versionCodeMention() {
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'VERSION_CODES' reference instead of the numeric value 23 [VersionCodeUsage]
|
||||
@RequiresApi(app = Build.VERSION_CODES.M)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with 23:
|
||||
@@ -5 +5
|
||||
- @RequiresApi(app = Build.VERSION_CODES.M)
|
||||
+ @RequiresApi(app = 23)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val requiresApiStub = kotlin(readResourceAsString("RequiresApiStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package androidx.appcompat.app
|
||||
|
||||
class AlertDialog {
|
||||
class Builder {
|
||||
constructor(context: Context?)
|
||||
constructor(context: Context?, themeOverrideId: Int)
|
||||
}
|
||||
}
|
||||
40
lintchecks/src/test/resources/AppLogStub.kt
Normal file
40
lintchecks/src/test/resources/AppLogStub.kt
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package org.signal.core.util.logging
|
||||
|
||||
object Log {
|
||||
fun tag(clazz: Class<*>?): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
}
|
||||
9
lintchecks/src/test/resources/CardViewStub.kt
Normal file
9
lintchecks/src/test/resources/CardViewStub.kt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package androidx.cardview.widget
|
||||
|
||||
class CardView {
|
||||
constructor(context: Context?)
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?)
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
|
||||
}
|
||||
6
lintchecks/src/test/resources/ContextCompatStub.kt
Normal file
6
lintchecks/src/test/resources/ContextCompatStub.kt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package androidx.core.content
|
||||
|
||||
object ContextCompat {
|
||||
fun startForegroundService(context: Context?, intent: Intent?) {
|
||||
}
|
||||
}
|
||||
6
lintchecks/src/test/resources/ContextStub.kt
Normal file
6
lintchecks/src/test/resources/ContextStub.kt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package android.content
|
||||
|
||||
class Context {
|
||||
fun startForegroundService(intent: Intent?) {
|
||||
}
|
||||
}
|
||||
40
lintchecks/src/test/resources/GlideLogStub.kt
Normal file
40
lintchecks/src/test/resources/GlideLogStub.kt
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package org.signal.glide
|
||||
|
||||
object Log {
|
||||
fun tag(clazz: Class<*>?): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
}
|
||||
18
lintchecks/src/test/resources/KotlinIOStub.kt
Normal file
18
lintchecks/src/test/resources/KotlinIOStub.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
@file:JvmName("ConsoleKt")
|
||||
|
||||
package kotlin.io
|
||||
|
||||
/**
|
||||
* Stub for kotlin.io.println function for testing purposes
|
||||
*/
|
||||
fun println(message: Any?) {
|
||||
// Stub implementation
|
||||
}
|
||||
|
||||
fun println() {
|
||||
// Stub implementation
|
||||
}
|
||||
|
||||
fun print(message: Any?) {
|
||||
// Stub implementation
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
internal interface RecipientIdDatabaseReference {
|
||||
fun remapRecipient(fromId: RecipientId?, toId: RecipientId?)
|
||||
}
|
||||
3
lintchecks/src/test/resources/RequiresApiStub.kt
Normal file
3
lintchecks/src/test/resources/RequiresApiStub.kt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package android.annotation
|
||||
|
||||
annotation class RequiresApi
|
||||
36
lintchecks/src/test/resources/ServiceLogStub.kt
Normal file
36
lintchecks/src/test/resources/ServiceLogStub.kt
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package org.signal.libsignal.protocol.logging
|
||||
|
||||
object Log {
|
||||
fun v(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
internal interface ThreadIdDatabaseReference {
|
||||
fun remapThread(fromId: Long, toId: Long)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue