Add lint to detect non-numeric version code checks.

master
Alan Evans 2020-07-10 11:18:12 -03:00 committed by Greyson Parrelli
parent 7fb55c0f51
commit f3dbe4416f
4 changed files with 216 additions and 1 deletions

View File

@ -14,7 +14,8 @@ public final class Registry extends IssueRegistry {
public List<Issue> getIssues() {
return Arrays.asList(SignalLogDetector.LOG_NOT_SIGNAL,
SignalLogDetector.LOG_NOT_APP,
SignalLogDetector.INLINE_TAG);
SignalLogDetector.INLINE_TAG,
VersionCodeDetector.VERSION_CODE_USAGE);
}
@Override

View File

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

View File

@ -12,6 +12,7 @@ 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"));

View File

@ -0,0 +1,122 @@
package org.signal.lint;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
@SuppressWarnings("UnstableApiUsage")
public final class VersionCodeDetectorTest {
@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(
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)");
}
}