Migrate linchecks to Kotlin

This commit is contained in:
Jameson Williams 2025-01-25 00:12:11 -06:00
parent ec840726fc
commit 172a265a52
52 changed files with 1993 additions and 1745 deletions

View file

@ -1,86 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UExpression;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public final class AlertDialogBuilderDetector extends Detector implements Detector.UastScanner {
static final Issue ALERT_DIALOG_BUILDER_USAGE = Issue.create("AlertDialogBuilderUsage",
"Creating dialog with AlertDialog.Builder instead of MaterialAlertDialogBuilder",
"Signal utilizes MaterialAlertDialogBuilder for more consistent and pleasant AlertDialogs.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(AlertDialogBuilderDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public @Nullable List<String> getApplicableConstructorTypes() {
return Arrays.asList("android.app.AlertDialog.Builder", "androidx.appcompat.app.AlertDialog.Builder");
}
@Override
public void visitConstructor(JavaContext context, @NotNull UCallExpression call, @NotNull PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, "android.app.AlertDialog.Builder")) {
LintFix fix = quickFixIssueAlertDialogBuilder(call);
context.report(ALERT_DIALOG_BUILDER_USAGE,
call,
context.getLocation(call),
"Using 'android.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder",
fix);
}
if (evaluator.isMemberInClass(method, "androidx.appcompat.app.AlertDialog.Builder")) {
LintFix fix = quickFixIssueAlertDialogBuilder(call);
context.report(ALERT_DIALOG_BUILDER_USAGE,
call,
context.getLocation(call),
"Using 'androidx.appcompat.app.AlertDialog.Builder' instead of com.google.android.material.dialog.MaterialAlertDialogBuilder",
fix);
}
}
private LintFix quickFixIssueAlertDialogBuilder(@NotNull UCallExpression alertBuilderCall) {
List<UExpression> arguments = alertBuilderCall.getValueArguments();
UExpression context = arguments.get(0);
String fixSource = "new com.google.android.material.dialog.MaterialAlertDialogBuilder";
switch (arguments.size()) {
case 1:
fixSource += String.format("(%s)", context);
break;
case 2:
UExpression themeOverride = arguments.get(1);
fixSource += String.format("(%s, %s)", context, themeOverride);
break;
default:
throw new IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments");
}
String builderCallSource = alertBuilderCall.asSourceString();
LintFix.GroupBuilder fixGrouper = fix().group();
fixGrouper.add(fix().replace().text(builderCallSource).shortenNames().reformat(true).with(fixSource).build());
return fixGrouper.build();
}
}

View file

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

View file

@ -1,70 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UCallExpression;
import java.util.List;
/**
* 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.
*/
@SuppressWarnings("UnstableApiUsage")
public final class BlockingGetDetector extends Detector implements Detector.UastScanner {
static final Issue UNSAFE_BLOCKING_GET = Issue.create("UnsafeBlockingGet",
"BlockingGet is considered unsafe and should be avoided.",
"Prefer exposing the Observable instead. If you need to block, use RxExtensions.safeBlockingGet",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(BlockingGetDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public List<String> getApplicableMethodNames() {
return List.of("blockingGet");
}
@Override
public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, "io.reactivex.rxjava3.core.Single")) {
context.report(UNSAFE_BLOCKING_GET,
context.getLocation(node),
"Using 'Single#blockingGet' instead of 'RxExtensions.safeBlockingGet'",
null);
}
if (evaluator.isMemberInClass(method, "io.reactivex.rxjava3.core.Observable")) {
context.report(UNSAFE_BLOCKING_GET,
context.getLocation(node),
"Using 'Observable#blockingGet' instead of 'RxExtensions.safeBlockingGet'",
null);
}
if (evaluator.isMemberInClass(method, "io.reactivex.rxjava3.core.Flowable")) {
context.report(UNSAFE_BLOCKING_GET,
context.getLocation(node),
"Using 'Flowable#blockingGet' instead of 'RxExtensions.safeBlockingGet'",
null);
}
}
}

View file

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

View file

@ -1,82 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UExpression;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public final class CardViewDetector extends Detector implements Detector.UastScanner {
static final Issue CARD_VIEW_USAGE = Issue.create("CardViewUsage",
"Utilizing CardView instead of MaterialCardView subclass",
"Signal utilizes MaterialCardView for more consistent and pleasant CardViews.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(CardViewDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public @Nullable List<String> getApplicableConstructorTypes() {
return Collections.singletonList("androidx.cardview.widget.CardView");
}
@Override
public void visitConstructor(JavaContext context, @NotNull UCallExpression call, @NotNull PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, "androidx.cardview.widget.CardView")) {
LintFix fix = quickFixIssueAlertDialogBuilder(call);
context.report(CARD_VIEW_USAGE,
call,
context.getLocation(call),
"Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView",
fix);
}
}
private LintFix quickFixIssueAlertDialogBuilder(@NotNull UCallExpression alertBuilderCall) {
List<UExpression> arguments = alertBuilderCall.getValueArguments();
UExpression context = arguments.get(0);
String fixSource = "new com.google.android.material.card.MaterialCardView";
//Context context, AttributeSet attrs, int defStyleAttr
switch (arguments.size()) {
case 1:
fixSource += String.format("(%s)", context);
break;
case 2:
UExpression attrs = arguments.get(1);
fixSource += String.format("(%s, %s)", context, attrs);
break;
case 3:
UExpression attributes = arguments.get(1);
UExpression defStyleAttr = arguments.get(2);
fixSource += String.format("(%s, %s, %s)", context, attributes, defStyleAttr);
break;
default:
throw new IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments");
}
String builderCallSource = alertBuilderCall.asSourceString();
LintFix.GroupBuilder fixGrouper = fix().group();
fixGrouper.add(fix().replace().text(builderCallSource).shortenNames().reformat(true).with(fixSource).build());
return fixGrouper.build();
}
}

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

View file

@ -1,88 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.SourceCodeScanner;
import com.intellij.psi.PsiField;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public final class RecipientIdDatabaseDetector extends Detector implements SourceCodeScanner {
static final Issue RECIPIENT_ID_DATABASE_REFERENCE_ISSUE = Issue.create("RecipientIdDatabaseReferenceUsage",
"Referencing a RecipientId in a database without implementing RecipientIdDatabaseReference.",
"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,
5,
Severity.ERROR,
new Implementation(RecipientIdDatabaseDetector.class, Scope.JAVA_FILE_SCOPE));
private static final Set<String> EXEMPTED_CLASSES = new HashSet<>() {{
add("org.thoughtcrime.securesms.database.RecipientDatabase");
}};
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(UClass.class);
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitClass(@NotNull UClass node) {
if (node.getQualifiedName() == null) {
return;
}
if (node.getExtendsList() == null) {
return;
}
if (EXEMPTED_CLASSES.contains(node.getQualifiedName())) {
return;
}
boolean doesNotExtendDatabase = Arrays.stream(node.getExtendsList().getReferencedTypes()).noneMatch(t -> "Database".equals(t.getClassName()));
if (doesNotExtendDatabase) {
return;
}
boolean implementsReference = Arrays.stream(node.getInterfaces()).anyMatch(i -> "org.thoughtcrime.securesms.database.RecipientIdDatabaseReference".equals(i.getQualifiedName()));
if (implementsReference) {
return;
}
List<PsiField> recipientFields = Arrays.stream(node.getAllFields())
.filter(f -> f.getType().equalsToText("java.lang.String"))
.filter(f -> f.getName().toLowerCase().contains("recipient"))
.collect(Collectors.toList());
for (PsiField field : recipientFields) {
context.report(RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
field,
context.getLocation(field),
"If you reference a RecipientId in your table, you must implement the RecipientIdDatabaseReference interface.",
null);
}
}
};
}
}

View file

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

View file

@ -1,39 +0,0 @@
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.ApiKt;
import com.android.tools.lint.detector.api.Issue;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public final class Registry extends IssueRegistry {
@Override
public Vendor getVendor() {
return new Vendor("Signal", "Signal", "Signal", "Signal");
}
@Override
public List<Issue> getIssues() {
return Arrays.asList(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);
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
}

View file

@ -0,0 +1,29 @@
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
)
override val api = CURRENT_API
}

View file

@ -1,114 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.java.JavaUSimpleNameReferenceExpression;
import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression;
import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public final class SignalLogDetector extends Detector implements Detector.UastScanner {
static final Issue LOG_NOT_SIGNAL = Issue.create("LogNotSignal",
"Logging call to Android Log instead of Signal's Logger",
"Signal has its own logger which must be used.",
Category.MESSAGES,
5,
Severity.ERROR,
new Implementation(SignalLogDetector.class, Scope.JAVA_FILE_SCOPE));
static final Issue LOG_NOT_APP = Issue.create("LogNotAppSignal",
"Logging call to Signal Service Log instead of App level Logger",
"Signal app layer has its own logger which must be used.",
Category.MESSAGES,
5,
Severity.ERROR,
new Implementation(SignalLogDetector.class, Scope.JAVA_FILE_SCOPE));
static final Issue INLINE_TAG = Issue.create("LogTagInlined",
"Use of an inline string in a TAG",
"Often a sign of left in temporary log statements, always use a tag constant.",
Category.MESSAGES,
5,
Severity.ERROR,
new Implementation(SignalLogDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethodCall(JavaContext context, @NotNull UCallExpression call, @NotNull PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, "android.util.Log")) {
LintFix fix = quickFixIssueLog(call);
context.report(LOG_NOT_SIGNAL, call, context.getLocation(call), "Using 'android.util.Log' instead of a Signal Logger", fix);
}
if (evaluator.isMemberInClass(method, "org.signal.glide.Log")) {
LintFix fix = quickFixIssueLog(call);
context.report(LOG_NOT_SIGNAL, call, context.getLocation(call), "Using 'org.signal.glide.Log' instead of a Signal Logger", fix);
}
if (evaluator.isMemberInClass(method, "org.signal.libsignal.protocol.logging.Log")) {
LintFix fix = quickFixIssueLog(call);
context.report(LOG_NOT_APP, call, context.getLocation(call), "Using Signal server logger instead of app level Logger", fix);
}
if (evaluator.isMemberInClass(method, "org.signal.core.util.logging.Log")) {
List<UExpression> arguments = call.getValueArguments();
UExpression tag = arguments.get(0);
if (!(tag instanceof JavaUSimpleNameReferenceExpression || tag instanceof KotlinUSimpleReferenceExpression || tag instanceof KotlinUQualifiedReferenceExpression)) {
context.report(INLINE_TAG, call, context.getLocation(call), "Not using a tag constant");
}
}
}
private LintFix quickFixIssueLog(@NotNull UCallExpression logCall) {
List<UExpression> arguments = logCall.getValueArguments();
String methodName = logCall.getMethodName();
UExpression tag = arguments.get(0);
String fixSource = "org.signal.core.util.logging.Log.";
switch (arguments.size()) {
case 2:
UExpression msgOrThrowable = arguments.get(1);
fixSource += String.format("%s(%s, %s)", methodName, tag, msgOrThrowable.asSourceString());
break;
case 3:
UExpression msg = arguments.get(1);
UExpression throwable = arguments.get(2);
fixSource += String.format("%s(%s, %s, %s)", methodName, tag, msg.asSourceString(), throwable.asSourceString());
break;
default:
throw new IllegalStateException("Log overloads should have 2 or 3 arguments");
}
String logCallSource = logCall.asSourceString();
LintFix.GroupBuilder fixGrouper = fix().group();
fixGrouper.add(fix().replace().text(logCallSource).shortenNames().reformat(true).with(fixSource).build());
return fixGrouper.build();
}
}

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

View file

@ -1,53 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UExpression;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public final class StartForegroundServiceDetector extends Detector implements Detector.UastScanner {
static final Issue START_FOREGROUND_SERVICE_ISSUE = Issue.create("StartForegroundServiceUsage",
"Starting a foreground service using ContextCompat.startForegroundService instead of ForegroundServiceUtil",
"Starting a foreground service may fail, and we should prefer our utils to make sure they're started correctly",
Category.MESSAGES,
5,
Severity.ERROR,
new Implementation(StartForegroundServiceDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("startForegroundService");
}
@Override
public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression call, @NotNull PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (context.getUastFile() != null && context.getUastFile().getClasses().stream().anyMatch(it -> "ForegroundServiceUtil".equals(it.getName()))) {
return;
}
if (evaluator.isMemberInClass(method, "androidx.core.content.ContextCompat")) {
context.report(START_FOREGROUND_SERVICE_ISSUE, call, context.getLocation(call), "Using 'ContextCompat.startForegroundService' instead of a ForegroundServiceUtil");
} else if (evaluator.isMemberInClass(method, "android.content.Context")) {
context.report(START_FOREGROUND_SERVICE_ISSUE, call, context.getLocation(call), "Using 'Context.startForegroundService' instead of a ForegroundServiceUtil");
}
}
}

View file

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

View file

@ -1,88 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.SourceCodeScanner;
import com.intellij.psi.PsiField;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public final class ThreadIdDatabaseDetector extends Detector implements SourceCodeScanner {
static final Issue THREAD_ID_DATABASE_REFERENCE_ISSUE = Issue.create("ThreadIdDatabaseReferenceUsage",
"Referencing a thread ID in a database without implementing ThreadIdDatabaseReference.",
"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,
5,
Severity.ERROR,
new Implementation(ThreadIdDatabaseDetector.class, Scope.JAVA_FILE_SCOPE));
private static final Set<String> EXEMPTED_CLASSES = new HashSet<>() {{
add("org.thoughtcrime.securesms.database.ThreadDatabase");
}};
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(UClass.class);
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitClass(@NotNull UClass node) {
if (node.getQualifiedName() == null) {
return;
}
if (node.getExtendsList() == null) {
return;
}
if (EXEMPTED_CLASSES.contains(node.getQualifiedName())) {
return;
}
boolean doesNotExtendDatabase = Arrays.stream(node.getExtendsList().getReferencedTypes()).noneMatch(t -> "Database".equals(t.getClassName()));
if (doesNotExtendDatabase) {
return;
}
boolean implementsReference = Arrays.stream(node.getInterfaces()).anyMatch(i -> "org.thoughtcrime.securesms.database.ThreadIdDatabaseReference".equals(i.getQualifiedName()));
if (implementsReference) {
return;
}
List<PsiField> recipientFields = Arrays.stream(node.getAllFields())
.filter(f -> f.getType().equalsToText("java.lang.String"))
.filter(f -> f.getName().toLowerCase().contains("thread"))
.collect(Collectors.toList());
for (PsiField field : recipientFields) {
context.report(THREAD_ID_DATABASE_REFERENCE_ISSUE,
field,
context.getLocation(field),
"If you reference a thread ID in your table, you must implement the ThreadIdDatabaseReference interface.",
null);
}
}
};
}
}

View file

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

View file

@ -1,91 +0,0 @@
package org.signal.lint;
import com.android.annotations.NonNull;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
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;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public final class VersionCodeDetector extends Detector implements Detector.UastScanner {
static final Issue VERSION_CODE_USAGE = Issue.create("VersionCodeUsage",
"Using 'VERSION_CODES' reference instead of the numeric value",
"Signal style is to use the numeric value.",
Category.CORRECTNESS,
5,
Severity.WARNING,
new Implementation(VersionCodeDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(UExpression.class);
}
@Override
public UElementHandler createUastHandler(@NonNull JavaContext context) {
return new ExpressionChecker(context);
}
private class ExpressionChecker extends UElementHandler {
private final JavaContext context;
private final JavaEvaluator evaluator;
private final PsiClass versionCodeClass;
public ExpressionChecker(JavaContext context) {
this.context = context;
this.evaluator = context.getEvaluator();
this.versionCodeClass = evaluator.findClass("android.os.Build.VERSION_CODES");
}
@Override
public void visitExpression(@NotNull UExpression node) {
if (versionCodeClass != null && node.getExpressionType() == PsiType.INT) {
PsiElement javaPsi = node.getJavaPsi();
if (javaPsi != null) {
PsiElement resolved = evaluator.resolve(javaPsi);
if (resolved != null && resolved.getParent().equals(versionCodeClass)) {
Object evaluated = node.evaluate();
if (evaluated != null) {
context.report(VERSION_CODE_USAGE, node, context.getLocation(node), "Using 'VERSION_CODES' reference instead of the numeric value " + evaluated, quickFixIssueInlineValue(node, evaluated.toString()));
} else {
context.report(VERSION_CODE_USAGE, node, context.getLocation(node), "Using 'VERSION_CODES' reference instead of the numeric value", null);
}
}
}
}
}
}
private LintFix quickFixIssueInlineValue(@NotNull UExpression node, @NotNull String fixSource) {
String expressionSource = node.asSourceString();
LintFix.GroupBuilder fixGrouper = fix().group();
fixGrouper.add(fix().replace()
.text(expressionSource)
.reformat(true)
.with(fixSource)
.build());
return fixGrouper.build();
}
}

View file

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

View file

@ -1,173 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class AlertDialogBuilderDetectorTest {
private static final TestFile appCompatAlertDialogStub = java(readResourceAsString("AppCompatAlertDialogStub.java"));
@Test
public void androidAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_1_arg() {
lint()
.files(
java("package foo;\n" +
"import android.app.AlertDialog;\n" +
"public class Example {\n" +
" public void buildDialog() {\n" +
" new AlertDialog.Builder(context).show();\n" +
" }\n" +
"}")
)
.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]\n" +
" new AlertDialog.Builder(context).show();\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):\n" +
"@@ -5 +5\n" +
"- new AlertDialog.Builder(context).show();\n" +
"+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context).show();");
}
@Test
public void androidAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_2_arg() {
lint()
.files(
java("package foo;\n" +
"import android.app.AlertDialog;\n" +
"public class Example {\n" +
" public void buildDialog() {\n" +
" new AlertDialog.Builder(context, themeOverride).show();\n" +
" }\n" +
"}")
)
.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]\n" +
" new AlertDialog.Builder(context, themeOverride).show();\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride):\n" +
"@@ -5 +5\n" +
"- new AlertDialog.Builder(context, themeOverride).show();\n" +
"+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride).show();");
}
@Test
public void androidAlertDialogBuilderUsed_withAssignment_LogAlertDialogBuilderUsage_1_arg() {
lint()
.files(
java("package foo;\n" +
"import android.app.AlertDialog;\n" +
"public class Example {\n" +
" public void buildDialog() {\n" +
" AlertDialog.Builder builder = new AlertDialog.Builder(context)\n" +
" .show();\n" +
" }\n" +
"}")
)
.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]\n" +
" AlertDialog.Builder builder = new AlertDialog.Builder(context)\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):\n" +
"@@ -5 +5\n" +
"- AlertDialog.Builder builder = new AlertDialog.Builder(context)\n" +
"+ AlertDialog.Builder builder = new com.google.android.material.dialog.MaterialAlertDialogBuilder(context)");
}
@Test
public void appcompatAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_1_arg() {
lint()
.files(appCompatAlertDialogStub,
java("package foo;\n" +
"import androidx.appcompat.app.AlertDialog;\n" +
"public class Example {\n" +
" public void buildDialog() {\n" +
" new AlertDialog.Builder(context).show();\n" +
" }\n" +
"}")
)
.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]\n" +
" new AlertDialog.Builder(context).show();\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):\n" +
"@@ -5 +5\n" +
"- new AlertDialog.Builder(context).show();\n" +
"+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context).show();");
}
@Test
public void appcompatAlertDialogBuilderUsed_LogAlertDialogBuilderUsage_2_arg() {
lint()
.files(appCompatAlertDialogStub,
java("package foo;\n" +
"import androidx.appcompat.app.AlertDialog;\n" +
"public class Example {\n" +
" public void buildDialog() {\n" +
" new AlertDialog.Builder(context, themeOverride).show();\n" +
" }\n" +
"}")
)
.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]\n" +
" new AlertDialog.Builder(context, themeOverride).show();\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride):\n" +
"@@ -5 +5\n" +
"- new AlertDialog.Builder(context, themeOverride).show();\n" +
"+ new com.google.android.material.dialog.MaterialAlertDialogBuilder(context, themeOverride).show();");
}
@Test
public void appcompatAlertDialogBuilderUsed_withAssignment_LogAlertDialogBuilderUsage_1_arg() {
lint()
.files(appCompatAlertDialogStub,
java("package foo;\n" +
"import androidx.appcompat.app.AlertDialog;\n" +
"public class Example {\n" +
" public void buildDialog() {\n" +
" AlertDialog.Builder builder = new AlertDialog.Builder(context)\n" +
" .show();\n" +
" }\n" +
"}")
)
.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]\n" +
" AlertDialog.Builder builder = new AlertDialog.Builder(context)\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.dialog.MaterialAlertDialogBuilder(context):\n" +
"@@ -5 +5\n" +
"- AlertDialog.Builder builder = new AlertDialog.Builder(context)\n" +
"+ AlertDialog.Builder builder = new com.google.android.material.dialog.MaterialAlertDialogBuilder(context)");
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

View file

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

View file

@ -1,100 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class CardViewDetectorTest {
private static final TestFile cardViewStub = java(readResourceAsString("CardViewStub.java"));
@Test
public void cardViewUsed_LogCardViewUsage_1_arg() {
lint()
.files(cardViewStub,
java("package foo;\n" +
"import androidx.cardview.widget.CardView;\n" +
"public class Example {\n" +
" public void buildCardView() {\n" +
" new CardView(context);\n" +
" }\n" +
"}")
)
.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]\n" +
" new CardView(context);\n" +
" ~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):\n" +
"@@ -5 +5\n" +
"- new CardView(context);\n" +
"+ new com.google.android.material.card.MaterialCardView(context);");
}
@Test
public void cardViewUsed_LogCardViewUsage_2_arg() {
lint()
.files(cardViewStub,
java("package foo;\n" +
"import androidx.cardview.widget.CardView;\n" +
"public class Example {\n" +
" public void buildCardView() {\n" +
" new CardView(context, attrs);\n" +
" }\n" +
"}")
)
.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]\n" +
" new CardView(context, attrs);\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context, attrs):\n" +
"@@ -5 +5\n" +
"- new CardView(context, attrs);\n" +
"+ new com.google.android.material.card.MaterialCardView(context, attrs);");
}
@Test
public void cardViewUsed_withAssignment_LogCardViewUsage_1_arg() {
lint()
.files(cardViewStub,
java("package foo;\n" +
"import androidx.cardview.widget.CardView;\n" +
"public class Example {\n" +
" public void buildCardView() {\n" +
" CardView cardView = new CardView(context)\n" +
" ;\n" +
" }\n" +
"}")
)
.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]\n" +
" CardView cardView = new CardView(context)\n" +
" ~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):\n" +
"@@ -5 +5\n" +
"- CardView cardView = new CardView(context)\n" +
"+ CardView cardView = new com.google.android.material.card.MaterialCardView(context)");
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

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

View file

@ -1,248 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestFiles.kotlin;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class LogDetectorTest {
private static final TestFile serviceLogStub = java(readResourceAsString("ServiceLogStub.java"));
private static final TestFile appLogStub = java(readResourceAsString("AppLogStub.java"));
private static final TestFile glideLogStub = java(readResourceAsString("GlideLogStub.java"));
@Test
public void androidLogUsed_LogNotSignal_2_args() {
lint()
.files(
java("package foo;\n" +
"import android.util.Log;\n" +
"public class Example {\n" +
" public void log() {\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
.run()
.expect("src/foo/Example.java:5: Error: Using 'android.util.Log' instead of a Signal Logger [LogNotSignal]\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d(\"TAG\", \"msg\"):\n" +
"@@ -5 +5\n" +
"- Log.d(\"TAG\", \"msg\");\n" +
"+ org.signal.core.util.logging.Log.d(\"TAG\", \"msg\");");
}
@Test
public void androidLogUsed_LogNotSignal_3_args() {
lint()
.files(
java("package foo;\n" +
"import android.util.Log;\n" +
"public class Example {\n" +
" public void log() {\n" +
" Log.w(\"TAG\", \"msg\", new Exception());\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
.run()
.expect("src/foo/Example.java:5: Error: Using 'android.util.Log' instead of a Signal Logger [LogNotSignal]\n" +
" Log.w(\"TAG\", \"msg\", new Exception());\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.w(\"TAG\", \"msg\", new Exception()):\n" +
"@@ -5 +5\n" +
"- Log.w(\"TAG\", \"msg\", new Exception());\n" +
"+ org.signal.core.util.logging.Log.w(\"TAG\", \"msg\", new Exception());");
}
@Test
public void signalServiceLogUsed_LogNotApp_2_args() {
lint()
.files(serviceLogStub,
java("package foo;\n" +
"import org.signal.libsignal.protocol.logging.Log;\n" +
"public class Example {\n" +
" public void log() {\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.LOG_NOT_APP)
.run()
.expect("src/foo/Example.java:5: Error: Using Signal server logger instead of app level Logger [LogNotAppSignal]\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d(\"TAG\", \"msg\"):\n" +
"@@ -5 +5\n" +
"- Log.d(\"TAG\", \"msg\");\n" +
"+ org.signal.core.util.logging.Log.d(\"TAG\", \"msg\");");
}
@Test
public void signalServiceLogUsed_LogNotApp_3_args() {
lint()
.files(serviceLogStub,
java("package foo;\n" +
"import org.signal.libsignal.protocol.logging.Log;\n" +
"public class Example {\n" +
" public void log() {\n" +
" Log.w(\"TAG\", \"msg\", new Exception());\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.LOG_NOT_APP)
.run()
.expect("src/foo/Example.java:5: Error: Using Signal server logger instead of app level Logger [LogNotAppSignal]\n" +
" Log.w(\"TAG\", \"msg\", new Exception());\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.w(\"TAG\", \"msg\", new Exception()):\n" +
"@@ -5 +5\n" +
"- Log.w(\"TAG\", \"msg\", new Exception());\n" +
"+ org.signal.core.util.logging.Log.w(\"TAG\", \"msg\", new Exception());");
}
@Test
public void log_uses_tag_constant() {
lint()
.files(appLogStub,
java("package foo;\n" +
"import org.signal.core.util.logging.Log;\n" +
"public class Example {\n" +
" private static final String TAG = Log.tag(Example.class);\n" +
" public void log() {\n" +
" Log.d(TAG, \"msg\");\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.INLINE_TAG)
.run()
.expectClean();
}
@Test
public void log_uses_tag_constant_kotlin() {
lint()
.files(appLogStub,
kotlin("package foo\n" +
"import org.signal.core.util.logging.Log\n" +
"class Example {\n" +
" const val TAG: String = Log.tag(Example::class.java)\n" +
" fun log() {\n" +
" Log.d(TAG, \"msg\")\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.INLINE_TAG)
.run()
.expectClean();
}
@Test
public void log_uses_tag_companion_kotlin() {
lint()
.files(appLogStub,
kotlin("package foo\n" +
"import org.signal.core.util.logging.Log\n" +
"class Example {\n" +
" companion object { val TAG: String = Log.tag(Example::class.java) }\n" +
" fun log() {\n" +
" Log.d(TAG, \"msg\")\n" +
" }\n" +
"}\n"+
"fun logOutsie() {\n" +
" Log.d(Example.TAG, \"msg\")\n" +
"}\n")
)
.issues(SignalLogDetector.INLINE_TAG)
.run()
.expectClean();
}
@Test
public void log_uses_inline_tag() {
lint()
.files(appLogStub,
java("package foo;\n" +
"import org.signal.core.util.logging.Log;\n" +
"public class Example {\n" +
" public void log() {\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.INLINE_TAG)
.run()
.expect("src/foo/Example.java:5: Error: Not using a tag constant [LogTagInlined]\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("");
}
@Test
public void log_uses_inline_tag_kotlin() {
lint()
.files(appLogStub,
kotlin("package foo\n" +
"import org.signal.core.util.logging.Log\n" +
"class Example {\n" +
" fun log() {\n" +
" Log.d(\"TAG\", \"msg\")\n" +
" }\n" +
"}"))
.issues(SignalLogDetector.INLINE_TAG)
.run()
.expect("src/foo/Example.kt:5: Error: Not using a tag constant [LogTagInlined]\n" +
" Log.d(\"TAG\", \"msg\")\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("");
}
@Test
public void glideLogUsed_LogNotSignal_2_args() {
lint()
.files(glideLogStub,
java("package foo;\n" +
"import org.signal.glide.Log;\n" +
"public class Example {\n" +
" public void log() {\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" }\n" +
"}")
)
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
.run()
.expect("src/foo/Example.java:5: Error: Using 'org.signal.glide.Log' instead of a Signal Logger [LogNotSignal]\n" +
" Log.d(\"TAG\", \"msg\");\n" +
" ~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d(\"TAG\", \"msg\"):\n" +
"@@ -5 +5\n" +
"- Log.d(\"TAG\", \"msg\");\n" +
"+ org.signal.core.util.logging.Log.d(\"TAG\", \"msg\");");
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

View file

@ -1,62 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class RecipientIdDatabaseDetectorTest {
private static final TestFile recipientReferenceStub = java(readResourceAsString("RecipientIdDatabaseReferenceStub.java"));
@Test
public void recipientIdDatabase_databaseHasRecipientFieldButDoesNotImplementInterface_showError() {
lint()
.files(
java("package foo;\n" +
"public class Example extends Database {\n" +
" private static final String RECIPIENT_ID = \"recipient_id\";\n" +
"}")
)
.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]\n" +
" private static final String RECIPIENT_ID = \"recipient_id\";\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings");
}
@Test
public void recipientIdDatabase_databaseHasRecipientFieldAndImplementsInterface_noError() {
lint()
.files(
recipientReferenceStub,
java("package foo;\n" +
"import org.thoughtcrime.securesms.database.RecipientIdDatabaseReference;\n" +
"public class Example extends Database implements RecipientIdDatabaseReference {\n" +
" private static final String RECIPIENT_ID = \"recipient_id\";\n" +
" @Override\n" +
" public void remapRecipient(RecipientId fromId, RecipientId toId) {}\n" +
"}")
)
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
.run()
.expectClean();
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

View file

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

View file

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

View file

@ -1,75 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestFiles.kotlin;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class StartForegroundServiceDetectorTest {
private static final TestFile contextCompatStub = java(readResourceAsString("ContextCompatStub.java"));
private static final TestFile contextStub = java(readResourceAsString("ContextStub.java"));
@Test
public void contextCompatUsed() {
lint()
.files(
contextCompatStub,
java("package foo;\n" +
"import androidx.core.content.ContextCompat;\n" +
"public class Example {\n" +
" public void start() {\n" +
" ContextCompat.startForegroundService(context, new Intent());\n" +
" }\n" +
"}")
)
.allowMissingSdk()
.issues(StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE)
.run()
.expect("src/foo/Example.java:5: Error: Using 'ContextCompat.startForegroundService' instead of a ForegroundServiceUtil [StartForegroundServiceUsage]\n" +
" ContextCompat.startForegroundService(context, new Intent());\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings");
}
@Test
public void contextUsed() {
lint()
.files(
contextStub,
java("package foo;\n" +
"import android.content.Context;\n" +
"public class Example {\n" +
" Context context;\n" +
" public void start() {\n" +
" context.startForegroundService(new Intent());\n" +
" }\n" +
"}")
)
.allowMissingSdk()
.issues(StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE)
.run()
.expect("src/foo/Example.java:6: Error: Using 'Context.startForegroundService' instead of a ForegroundServiceUtil [StartForegroundServiceUsage]\n" +
" context.startForegroundService(new Intent());\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings");
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

View file

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

View file

@ -1,62 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class ThreadIdDatabaseDetectorTest {
private static final TestFile threadReferenceStub = java(readResourceAsString("ThreadIdDatabaseReferenceStub.java"));
@Test
public void threadIdDatabase_databaseHasThreadFieldButDoesNotImplementInterface_showError() {
lint()
.files(
java("package foo;\n" +
"public class Example extends Database {\n" +
" private static final String THREAD_ID = \"thread_id\";\n" +
"}")
)
.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]\n" +
" private static final String THREAD_ID = \"thread_id\";\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings");
}
@Test
public void threadIdDatabase_databaseHasThreadFieldAndImplementsInterface_noError() {
lint()
.files(
threadReferenceStub,
java("package foo;\n" +
"import org.thoughtcrime.securesms.database.ThreadIdDatabaseReference;\n" +
"public class Example extends Database implements ThreadIdDatabaseReference {\n" +
" private static final String THREAD_ID = \"thread_id\";\n" +
" @Override\n" +
" public void remapThread(long fromId, long toId) {}\n" +
"}")
)
.issues(ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE)
.run()
.expectClean();
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

View file

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

View file

@ -1,140 +0,0 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class VersionCodeDetectorTest {
private static final TestFile requiresApiStub = java(readResourceAsString("RequiresApiStub.java"));
@Test
public void version_code_constant_referenced_in_code() {
lint()
.files(
java("package foo;\n" +
"import android.os.Build;\n" +
"public class Example {\n" +
" public void versionCodeMention() {\n" +
" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n" +
" }\n" +
" }\n" +
"}")
)
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
.run()
.expect("src/foo/Example.java:5: Warning: Using 'VERSION_CODES' reference instead of the numeric value 21 [VersionCodeUsage]\n" +
" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with 21:\n" +
"@@ -5 +5\n" +
"- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n" +
"+ if (Build.VERSION.SDK_INT >= 21) {");
}
@Test
public void numeric_value_referenced_in_code() {
lint()
.files(
java("package foo;\n" +
"import android.os.Build;\n" +
"public class Example {\n" +
" public void versionCodeMention() {\n" +
" if (Build.VERSION.SDK_INT >= 22) {\n" +
" }\n" +
" }\n" +
"}")
)
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
.run()
.expectClean();
}
@Test
public void non_version_code_constant_referenced_in_code() {
lint()
.files(
java("package foo;\n" +
"import android.os.Build;\n" +
"public class Example {\n" +
" private final static int LOLLIPOP = 21;\n" +
" public void versionCodeMention() {\n" +
" if (Build.VERSION.SDK_INT >= LOLLIPOP) {\n" +
" }\n" +
" }\n" +
"}")
)
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
.run()
.expectClean();
}
@Test
public void version_code_constant_referenced_in_TargetApi_attribute_and_inner_class_import() {
lint()
.files(
java("package foo;\n" +
"import android.os.Build.VERSION_CODES;\n" +
"import android.annotation.TargetApi;\n" +
"public class Example {\n" +
" @TargetApi(VERSION_CODES.N)\n" +
" public void versionCodeMention() {\n" +
" }\n" +
"}")
)
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
.run()
.expect("src/foo/Example.java:5: Warning: Using 'VERSION_CODES' reference instead of the numeric value 24 [VersionCodeUsage]\n" +
" @TargetApi(VERSION_CODES.N)\n" +
" ~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with 24:\n" +
"@@ -5 +5\n" +
"- @TargetApi(VERSION_CODES.N)\n" +
"+ @TargetApi(24)");
}
@Test
public void version_code_constant_referenced_in_RequiresApi_attribute_with_named_parameter() {
lint()
.files(
requiresApiStub,
java("package foo;\n" +
"import android.os.Build;\n" +
"import android.annotation.RequiresApi;\n" +
"public class Example {\n" +
" @RequiresApi(app = Build.VERSION_CODES.M)\n" +
" public void versionCodeMention() {\n" +
" }\n" +
"}")
)
.issues(VersionCodeDetector.VERSION_CODE_USAGE)
.run()
.expect("src/foo/Example.java:5: Warning: Using 'VERSION_CODES' reference instead of the numeric value 23 [VersionCodeUsage]\n" +
" @RequiresApi(app = Build.VERSION_CODES.M)\n" +
" ~~~~~~~~~~~~~~~~~~~~~\n" +
"0 errors, 1 warnings")
.expectFixDiffs("Fix for src/foo/Example.java line 5: Replace with 23:\n" +
"@@ -5 +5\n" +
"- @RequiresApi(app = Build.VERSION_CODES.M)\n" +
"+ @RequiresApi(app = 23)");
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

View file

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

View file

@ -1,13 +0,0 @@
package androidx.appcompat.app;
public class AlertDialog {
public static class Builder {
public Builder(Context context) {
}
public Builder(Context context, int themeOverrideId) {
}
}
}

View file

@ -0,0 +1,8 @@
package androidx.appcompat.app
class AlertDialog {
class Builder {
constructor(context: Context?)
constructor(context: Context?, themeOverrideId: Int)
}
}

View file

@ -1,41 +0,0 @@
package org.signal.core.util.logging;
public class Log {
public static String tag(Class<?> clazz) {
return "";
}
public static void v(String tag, String msg) {
}
public static void v(String tag, String msg, Throwable tr) {
}
public static void d(String tag, String msg) {
}
public static void d(String tag, String msg, Throwable tr) {
}
public static void i(String tag, String msg) {
}
public static void i(String tag, String msg, Throwable tr) {
}
public static void w(String tag, String msg) {
}
public static void w(String tag, String msg, Throwable tr) {
}
public static void w(String tag, Throwable tr) {
}
public static void e(String tag, String msg) {
}
public static void e(String tag, String msg, Throwable tr) {
}
}

View 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?) {
}
}

View file

@ -1,16 +0,0 @@
package androidx.cardview.widget;
public class CardView {
public CardView(Context context) {
}
public CardView(Context context, AttributeSet attrs) {
}
public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
}
}

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

View file

@ -1,6 +0,0 @@
package androidx.core.content;
public class ContextCompat {
public static void startForegroundService(Context context, Intent intent) {
}
}

View file

@ -0,0 +1,6 @@
package androidx.core.content
object ContextCompat {
fun startForegroundService(context: Context?, intent: Intent?) {
}
}

View file

@ -1,6 +0,0 @@
package android.content;
public class Context {
public void startForegroundService(Intent intent) {
}
}

View file

@ -0,0 +1,6 @@
package android.content
class Context {
fun startForegroundService(intent: Intent?) {
}
}

View file

@ -1,41 +0,0 @@
package org.signal.glide;
public class Log {
public static String tag(Class<?> clazz) {
return "";
}
public static void v(String tag, String msg) {
}
public static void v(String tag, String msg, Throwable tr) {
}
public static void d(String tag, String msg) {
}
public static void d(String tag, String msg, Throwable tr) {
}
public static void i(String tag, String msg) {
}
public static void i(String tag, String msg, Throwable tr) {
}
public static void w(String tag, String msg) {
}
public static void w(String tag, String msg, Throwable tr) {
}
public static void w(String tag, Throwable tr) {
}
public static void e(String tag, String msg) {
}
public static void e(String tag, String msg, Throwable tr) {
}
}

View 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?) {
}
}

View file

@ -1,5 +0,0 @@
package org.thoughtcrime.securesms.database;
interface RecipientIdDatabaseReference {
void remapRecipient(RecipientId fromId, RecipientId toId);
}

View file

@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.database
internal interface RecipientIdDatabaseReference {
fun remapRecipient(fromId: RecipientId?, toId: RecipientId?)
}

View file

@ -1,4 +0,0 @@
package android.annotation;
public @interface RequiresApi {
}

View file

@ -0,0 +1,3 @@
package android.annotation
annotation class RequiresApi

View file

@ -1,37 +0,0 @@
package org.signal.libsignal.protocol.logging;
public class Log {
public static void v(String tag, String msg) {
}
public static void v(String tag, String msg, Throwable tr) {
}
public static void d(String tag, String msg) {
}
public static void d(String tag, String msg, Throwable tr) {
}
public static void i(String tag, String msg) {
}
public static void i(String tag, String msg, Throwable tr) {
}
public static void w(String tag, String msg) {
}
public static void w(String tag, String msg, Throwable tr) {
}
public static void w(String tag, Throwable tr) {
}
public static void e(String tag, String msg) {
}
public static void e(String tag, String msg, Throwable tr) {
}
}

View 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?) {
}
}

View file

@ -1,5 +0,0 @@
package org.thoughtcrime.securesms.database;
interface ThreadIdDatabaseReference {
void remapThread(long fromId, long toId);
}

View file

@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.database
internal interface ThreadIdDatabaseReference {
fun remapThread(fromId: Long, toId: Long)
}