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="LogTagInlined" severity="error" />
|
||||
|
||||
<issue id="AlertDialogBuilderUsage" severity="warning" />
|
||||
|
||||
<issue id="RestrictedApi" severity="error">
|
||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.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,
|
||||
SignalLogDetector.LOG_NOT_APP,
|
||||
SignalLogDetector.INLINE_TAG,
|
||||
VersionCodeDetector.VERSION_CODE_USAGE);
|
||||
VersionCodeDetector.VERSION_CODE_USAGE,
|
||||
AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE);
|
||||
}
|
||||
|
||||
@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