Add custom lint for AlertDialog.Builder usage.
This commit is contained in:
parent
e096ba27ce
commit
ab7f507b03
5 changed files with 276 additions and 1 deletions
|
@ -31,6 +31,8 @@
|
||||||
<issue id="LogNotAppSignal" severity="error" />
|
<issue id="LogNotAppSignal" severity="error" />
|
||||||
<issue id="LogTagInlined" severity="error" />
|
<issue id="LogTagInlined" severity="error" />
|
||||||
|
|
||||||
|
<issue id="AlertDialogBuilderUsage" severity="warning" />
|
||||||
|
|
||||||
<issue id="RestrictedApi" severity="error">
|
<issue id="RestrictedApi" severity="error">
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ public final class Registry extends IssueRegistry {
|
||||||
return Arrays.asList(SignalLogDetector.LOG_NOT_SIGNAL,
|
return Arrays.asList(SignalLogDetector.LOG_NOT_SIGNAL,
|
||||||
SignalLogDetector.LOG_NOT_APP,
|
SignalLogDetector.LOG_NOT_APP,
|
||||||
SignalLogDetector.INLINE_TAG,
|
SignalLogDetector.INLINE_TAG,
|
||||||
VersionCodeDetector.VERSION_CODE_USAGE);
|
VersionCodeDetector.VERSION_CODE_USAGE,
|
||||||
|
AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
13
lintchecks/src/test/resources/AppCompatAlertDialogStub.java
Normal file
13
lintchecks/src/test/resources/AppCompatAlertDialogStub.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package androidx.appcompat.app;
|
||||||
|
|
||||||
|
public class AlertDialog {
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
public Builder(Context context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(Context context, int themeOverrideId) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue