Add lint to detect non-numeric version code checks.
This commit is contained in:
parent
7fb55c0f51
commit
f3dbe4416f
4 changed files with 216 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue