Migrate linchecks to Kotlin
This commit is contained in:
parent
ec840726fc
commit
172a265a52
52 changed files with 1993 additions and 1745 deletions
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
81
lintchecks/src/main/java/org/signal/lint/CardViewDetector.kt
Normal file
81
lintchecks/src/main/java/org/signal/lint/CardViewDetector.kt
Normal file
|
@ -0,0 +1,81 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Implementation
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.WARNING
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
|
||||
class CardViewDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableConstructorTypes(): List<String> {
|
||||
return listOf("androidx.cardview.widget.CardView")
|
||||
}
|
||||
|
||||
override fun visitConstructor(context: JavaContext, node: UCallExpression, constructor: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(constructor, "androidx.cardview.widget.CardView")) {
|
||||
context.report(
|
||||
issue = CARD_VIEW_USAGE,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView",
|
||||
quickfixData = quickFixIssueAlertDialogBuilder(node)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun quickFixIssueAlertDialogBuilder(alertBuilderCall: UCallExpression): LintFix {
|
||||
val arguments = alertBuilderCall.valueArguments
|
||||
val context = arguments[0]
|
||||
|
||||
var fixSource = "new com.google.android.material.card.MaterialCardView"
|
||||
|
||||
//Context context, AttributeSet attrs, int defStyleAttr
|
||||
when (arguments.size) {
|
||||
1 -> fixSource += String.format("(%s)", context)
|
||||
2 -> {
|
||||
val attrs = arguments[1]
|
||||
fixSource += String.format("(%s, %s)", context, attrs)
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val attributes = arguments[1]
|
||||
val defStyleAttr = arguments[2]
|
||||
fixSource += String.format("(%s, %s, %s)", context, attributes, defStyleAttr)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments")
|
||||
}
|
||||
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(alertBuilderCall.asSourceString())
|
||||
.shortenNames()
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CARD_VIEW_USAGE: Issue = Issue.create(
|
||||
id = "CardViewUsage",
|
||||
briefDescription = "Utilizing CardView instead of MaterialCardView subclass",
|
||||
explanation = "Signal utilizes MaterialCardView for more consistent and pleasant CardViews.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = WARNING,
|
||||
implementation = Implementation(CardViewDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
29
lintchecks/src/main/java/org/signal/lint/Registry.kt
Normal file
29
lintchecks/src/main/java/org/signal/lint/Registry.kt
Normal 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
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
145
lintchecks/src/main/java/org/signal/lint/SignalLogDetector.kt
Normal file
145
lintchecks/src/main/java/org/signal/lint/SignalLogDetector.kt
Normal file
|
@ -0,0 +1,145 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.detector.api.Category.Companion.MESSAGES
|
||||
import com.android.tools.lint.detector.api.Detector
|
||||
import com.android.tools.lint.detector.api.Implementation
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.LintFix
|
||||
import com.android.tools.lint.detector.api.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.intellij.psi.PsiMethod
|
||||
import org.jetbrains.uast.UCallExpression
|
||||
import org.jetbrains.uast.java.JavaUSimpleNameReferenceExpression
|
||||
import org.jetbrains.uast.kotlin.KotlinUQualifiedReferenceExpression
|
||||
import org.jetbrains.uast.kotlin.KotlinUSimpleReferenceExpression
|
||||
|
||||
class SignalLogDetector : Detector(), Detector.UastScanner {
|
||||
override fun getApplicableMethodNames(): List<String> {
|
||||
return listOf("v", "d", "i", "w", "e", "wtf")
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
|
||||
val evaluator = context.evaluator
|
||||
|
||||
if (evaluator.isMemberInClass(method, "android.util.Log")) {
|
||||
context.report(
|
||||
issue = LOG_NOT_SIGNAL,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'android.util.Log' instead of a Signal Logger",
|
||||
quickfixData = quickFixIssueLog(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "org.signal.glide.Log")) {
|
||||
context.report(
|
||||
issue = LOG_NOT_SIGNAL,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using 'org.signal.glide.Log' instead of a Signal Logger",
|
||||
quickfixData = quickFixIssueLog(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "org.signal.libsignal.protocol.logging.Log")) {
|
||||
context.report(
|
||||
issue = LOG_NOT_APP,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Using Signal server logger instead of app level Logger",
|
||||
quickfixData = quickFixIssueLog(node)
|
||||
)
|
||||
}
|
||||
|
||||
if (evaluator.isMemberInClass(method, "org.signal.core.util.logging.Log")) {
|
||||
val arguments = node.valueArguments
|
||||
val tag = arguments[0]
|
||||
|
||||
val invalidTagType = setOf(
|
||||
JavaUSimpleNameReferenceExpression::class,
|
||||
KotlinUSimpleReferenceExpression::class,
|
||||
KotlinUQualifiedReferenceExpression::class
|
||||
).none { it.isInstance(tag) }
|
||||
|
||||
if (invalidTagType) {
|
||||
context.report(
|
||||
issue = INLINE_TAG,
|
||||
scope = node,
|
||||
location = context.getLocation(node),
|
||||
message = "Not using a tag constant"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun quickFixIssueLog(logCall: UCallExpression): LintFix {
|
||||
val arguments = logCall.valueArguments
|
||||
val methodName = logCall.methodName
|
||||
val tag = arguments[0]
|
||||
|
||||
var fixSource = "org.signal.core.util.logging.Log."
|
||||
|
||||
when (arguments.size) {
|
||||
2 -> {
|
||||
val msgOrThrowable = arguments[1]
|
||||
fixSource += String.format("%s(%s, %s)", methodName, tag, msgOrThrowable.asSourceString())
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val msg = arguments[1]
|
||||
val throwable = arguments[2]
|
||||
fixSource += String.format("%s(%s, %s, %s)", methodName, tag, msg.asSourceString(), throwable.asSourceString())
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("Log overloads should have 2 or 3 arguments")
|
||||
}
|
||||
|
||||
return fix()
|
||||
.group()
|
||||
.add(
|
||||
fix()
|
||||
.replace()
|
||||
.text(logCall.asSourceString())
|
||||
.shortenNames()
|
||||
.reformat(true)
|
||||
.with(fixSource)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val LOG_NOT_SIGNAL: Issue = Issue.create(
|
||||
id = "LogNotSignal",
|
||||
briefDescription = "Logging call to Android Log instead of Signal's Logger",
|
||||
explanation = "Signal has its own logger which must be used.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SignalLogDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
val LOG_NOT_APP: Issue = Issue.create(
|
||||
id = "LogNotAppSignal",
|
||||
briefDescription = "Logging call to Signal Service Log instead of App level Logger",
|
||||
explanation = "Signal app layer has its own logger which must be used.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SignalLogDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
|
||||
val INLINE_TAG: Issue = Issue.create(
|
||||
id = "LogTagInlined",
|
||||
briefDescription = "Use of an inline string in a TAG",
|
||||
explanation = "Often a sign of left in temporary log statements, always use a tag constant.",
|
||||
category = MESSAGES,
|
||||
priority = 5,
|
||||
severity = ERROR,
|
||||
implementation = Implementation(SignalLogDetector::class.java, JAVA_FILE_SCOPE)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
135
lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.kt
Normal file
135
lintchecks/src/test/java/org/signal/lint/CardViewDetectorTest.kt
Normal file
|
@ -0,0 +1,135 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class CardViewDetectorTest {
|
||||
@Test
|
||||
fun cardViewUsed_LogCardViewUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
cardViewStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.cardview.widget.CardView;
|
||||
public class Example {
|
||||
public void buildCardView() {
|
||||
new CardView(context);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(CardViewDetector.CARD_VIEW_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]
|
||||
new CardView(context);
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):
|
||||
@@ -5 +5
|
||||
- new CardView(context);
|
||||
+ new com.google.android.material.card.MaterialCardView(context);
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cardViewUsed_LogCardViewUsage_2_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
cardViewStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.cardview.widget.CardView;
|
||||
public class Example {
|
||||
public void buildCardView() {
|
||||
new CardView(context, attrs);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(CardViewDetector.CARD_VIEW_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]
|
||||
new CardView(context, attrs);
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
"""
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context, attrs):
|
||||
@@ -5 +5
|
||||
- new CardView(context, attrs);
|
||||
+ new com.google.android.material.card.MaterialCardView(context, attrs);
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cardViewUsed_withAssignment_LogCardViewUsage_1_arg() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
cardViewStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import androidx.cardview.widget.CardView;
|
||||
public class Example {
|
||||
public void buildCardView() {
|
||||
CardView cardView = new CardView(context)
|
||||
;
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(CardViewDetector.CARD_VIEW_USAGE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Warning: Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView [CardViewUsage]
|
||||
CardView cardView = new CardView(context)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
0 errors, 1 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with new com.google.android.material.card.MaterialCardView(context):
|
||||
@@ -5 +5
|
||||
- CardView cardView = new CardView(context)
|
||||
+ CardView cardView = new com.google.android.material.card.MaterialCardView(context)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val cardViewStub = kotlin(readResourceAsString("CardViewStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class RecipientIdDatabaseDetectorTest {
|
||||
@Test
|
||||
fun recipientIdDatabase_databaseHasRecipientFieldButDoesNotImplementInterface_showError() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
public class Example extends Database {
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:3: Error: If you reference a RecipientId in your table, you must implement the RecipientIdDatabaseReference interface. [RecipientIdDatabaseReferenceUsage]
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recipientIdDatabase_databaseHasRecipientFieldAndImplementsInterface_noError() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
recipientReferenceStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.thoughtcrime.securesms.database.RecipientIdDatabaseReference;
|
||||
public class Example extends Database implements RecipientIdDatabaseReference {
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
@Override
|
||||
public void remapRecipient(RecipientId fromId, RecipientId toId) {}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val recipientReferenceStub = kotlin(readResourceAsString("RecipientIdDatabaseReferenceStub.kt"))
|
||||
|
||||
private fun readResourceAsString(@Suppress("SameParameterValue") resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
package org.signal.lint
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.java
|
||||
import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
|
||||
import com.android.tools.lint.checks.infrastructure.TestLintTask
|
||||
import com.android.tools.lint.checks.infrastructure.TestMode
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.Scanner
|
||||
|
||||
class SignalLogDetectorTest {
|
||||
@Test
|
||||
fun androidLogUsed_LogNotSignal_2_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.util.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'android.util.Log' instead of a Signal Logger [LogNotSignal]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d("TAG", "msg"):
|
||||
@@ -5 +5
|
||||
- Log.d("TAG", "msg");
|
||||
+ org.signal.core.util.logging.Log.d("TAG", "msg");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun androidLogUsed_LogNotSignal_3_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import android.util.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'android.util.Log' instead of a Signal Logger [LogNotSignal]
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.w("TAG", "msg", new Exception()):
|
||||
@@ -5 +5
|
||||
- Log.w("TAG", "msg", new Exception());
|
||||
+ org.signal.core.util.logging.Log.w("TAG", "msg", new Exception());
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signalServiceLogUsed_LogNotApp_2_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
serviceLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_APP)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using Signal server logger instead of app level Logger [LogNotAppSignal]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d("TAG", "msg"):
|
||||
@@ -5 +5
|
||||
- Log.d("TAG", "msg");
|
||||
+ org.signal.core.util.logging.Log.d("TAG", "msg");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun signalServiceLogUsed_LogNotApp_3_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
serviceLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_APP)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using Signal server logger instead of app level Logger [LogNotAppSignal]
|
||||
Log.w("TAG", "msg", new Exception());
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.w("TAG", "msg", new Exception()):
|
||||
@@ -5 +5
|
||||
- Log.w("TAG", "msg", new Exception());
|
||||
+ org.signal.core.util.logging.Log.w("TAG", "msg", new Exception());
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_tag_constant() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.core.util.logging.Log;
|
||||
public class Example {
|
||||
private static final String TAG = Log.tag(Example.class);
|
||||
public void log() {
|
||||
Log.d(TAG, "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_tag_constant_kotlin() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import org.signal.core.util.logging.Log
|
||||
class Example {
|
||||
const val TAG: String = Log.tag(Example::class.java)
|
||||
fun log() {
|
||||
Log.d(TAG, "msg")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.skipTestModes(TestMode.REORDER_ARGUMENTS)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_tag_companion_kotlin() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import org.signal.core.util.logging.Log
|
||||
class Example {
|
||||
companion object { val TAG: String = Log.tag(Example::class.java) }
|
||||
fun log() {
|
||||
Log.d(TAG, "msg")
|
||||
}
|
||||
}
|
||||
fun logOutsie() {
|
||||
Log.d(Example.TAG, "msg")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.skipTestModes(TestMode.REORDER_ARGUMENTS)
|
||||
.run()
|
||||
.expectClean()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_inline_tag() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.core.util.logging.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Not using a tag constant [LogTagInlined]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun log_uses_inline_tag_kotlin() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
appLogStub,
|
||||
kotlin(
|
||||
"""
|
||||
package foo
|
||||
import org.signal.core.util.logging.Log
|
||||
class Example {
|
||||
fun log() {
|
||||
Log.d("TAG", "msg")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.INLINE_TAG)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.kt:5: Error: Not using a tag constant [LogTagInlined]
|
||||
Log.d("TAG", "msg")
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs("")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun glideLogUsed_LogNotSignal_2_args() {
|
||||
TestLintTask.lint()
|
||||
.files(
|
||||
glideLogStub,
|
||||
java(
|
||||
"""
|
||||
package foo;
|
||||
import org.signal.glide.Log;
|
||||
public class Example {
|
||||
public void log() {
|
||||
Log.d("TAG", "msg");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
.issues(SignalLogDetector.LOG_NOT_SIGNAL)
|
||||
.run()
|
||||
.expect(
|
||||
"""
|
||||
src/foo/Example.java:5: Error: Using 'org.signal.glide.Log' instead of a Signal Logger [LogNotSignal]
|
||||
Log.d("TAG", "msg");
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
1 errors, 0 warnings
|
||||
""".trimIndent()
|
||||
)
|
||||
.expectFixDiffs(
|
||||
"""
|
||||
Fix for src/foo/Example.java line 5: Replace with org.signal.core.util.logging.Log.d("TAG", "msg"):
|
||||
@@ -5 +5
|
||||
- Log.d("TAG", "msg");
|
||||
+ org.signal.core.util.logging.Log.d("TAG", "msg");
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val serviceLogStub = kotlin(readResourceAsString("ServiceLogStub.kt"))
|
||||
private val appLogStub = kotlin(readResourceAsString("AppLogStub.kt"))
|
||||
private val glideLogStub = kotlin(readResourceAsString("GlideLogStub.kt"))
|
||||
|
||||
private fun readResourceAsString(resourceName: String): String {
|
||||
val inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)
|
||||
assertNotNull(inputStream)
|
||||
val scanner = Scanner(inputStream!!).useDelimiter("\\A")
|
||||
assertTrue(scanner.hasNext())
|
||||
return scanner.next()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package androidx.appcompat.app
|
||||
|
||||
class AlertDialog {
|
||||
class Builder {
|
||||
constructor(context: Context?)
|
||||
constructor(context: Context?, themeOverrideId: Int)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
40
lintchecks/src/test/resources/AppLogStub.kt
Normal file
40
lintchecks/src/test/resources/AppLogStub.kt
Normal file
|
@ -0,0 +1,40 @@
|
|||
package org.signal.core.util.logging
|
||||
|
||||
object Log {
|
||||
fun tag(clazz: Class<*>?): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
9
lintchecks/src/test/resources/CardViewStub.kt
Normal file
9
lintchecks/src/test/resources/CardViewStub.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
package androidx.cardview.widget
|
||||
|
||||
class CardView {
|
||||
constructor(context: Context?)
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?)
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package androidx.core.content;
|
||||
|
||||
public class ContextCompat {
|
||||
public static void startForegroundService(Context context, Intent intent) {
|
||||
}
|
||||
}
|
6
lintchecks/src/test/resources/ContextCompatStub.kt
Normal file
6
lintchecks/src/test/resources/ContextCompatStub.kt
Normal file
|
@ -0,0 +1,6 @@
|
|||
package androidx.core.content
|
||||
|
||||
object ContextCompat {
|
||||
fun startForegroundService(context: Context?, intent: Intent?) {
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package android.content;
|
||||
|
||||
public class Context {
|
||||
public void startForegroundService(Intent intent) {
|
||||
}
|
||||
}
|
6
lintchecks/src/test/resources/ContextStub.kt
Normal file
6
lintchecks/src/test/resources/ContextStub.kt
Normal file
|
@ -0,0 +1,6 @@
|
|||
package android.content
|
||||
|
||||
class Context {
|
||||
fun startForegroundService(intent: Intent?) {
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
40
lintchecks/src/test/resources/GlideLogStub.kt
Normal file
40
lintchecks/src/test/resources/GlideLogStub.kt
Normal file
|
@ -0,0 +1,40 @@
|
|||
package org.signal.glide
|
||||
|
||||
object Log {
|
||||
fun tag(clazz: Class<*>?): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
interface RecipientIdDatabaseReference {
|
||||
void remapRecipient(RecipientId fromId, RecipientId toId);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
internal interface RecipientIdDatabaseReference {
|
||||
fun remapRecipient(fromId: RecipientId?, toId: RecipientId?)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package android.annotation;
|
||||
|
||||
public @interface RequiresApi {
|
||||
}
|
3
lintchecks/src/test/resources/RequiresApiStub.kt
Normal file
3
lintchecks/src/test/resources/RequiresApiStub.kt
Normal file
|
@ -0,0 +1,3 @@
|
|||
package android.annotation
|
||||
|
||||
annotation class RequiresApi
|
|
@ -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) {
|
||||
}
|
||||
}
|
36
lintchecks/src/test/resources/ServiceLogStub.kt
Normal file
36
lintchecks/src/test/resources/ServiceLogStub.kt
Normal file
|
@ -0,0 +1,36 @@
|
|||
package org.signal.libsignal.protocol.logging
|
||||
|
||||
object Log {
|
||||
fun v(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun v(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun d(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun i(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun w(tag: String?, tr: Throwable?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?) {
|
||||
}
|
||||
|
||||
fun e(tag: String?, msg: String?, tr: Throwable?) {
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
interface ThreadIdDatabaseReference {
|
||||
void remapThread(long fromId, long toId);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.thoughtcrime.securesms.database
|
||||
|
||||
internal interface ThreadIdDatabaseReference {
|
||||
fun remapThread(fromId: Long, toId: Long)
|
||||
}
|
Loading…
Add table
Reference in a new issue