0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-19 19:42:17 +02:00

Add lint checks for new time api

This commit is contained in:
lukstbit 2020-08-23 16:52:54 +03:00 committed by Arthur Milchior
parent 7095a48c01
commit de3f38ee37
20 changed files with 906 additions and 0 deletions

View File

@ -274,4 +274,7 @@ dependencies {
//For AnkiDroid JS API Versioning
implementation "com.github.zafarkhaja:java-semver:0.9.0"
// Adds the custom lint rules
lintChecks project(":lint-rules")
}

View File

@ -23,6 +23,11 @@ import java.util.Date;
import java.util.GregorianCalendar;
public class SystemTime extends Time {
public SystemTime() {
}
@Override
public long intTimeMS() {
return System.currentTimeMillis();

View File

@ -52,6 +52,8 @@ dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'org.junit.vintage:junit-vintage-engine:5.6.2'
testImplementation 'org.robolectric:robolectric:4.3.1'
lintChecks project(":lint-rules")
}
// Borrowed from here:

1
lint-rules/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

20
lint-rules/build.gradle Normal file
View File

@ -0,0 +1,20 @@
apply plugin: "java-library"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
google()
jcenter()
}
dependencies {
compileOnly "com.android.tools.lint:lint-api:27.0.1"
compileOnly "com.android.tools.lint:lint:27.0.1"
testImplementation "junit:junit:4.13"
testImplementation "com.android.tools.lint:lint:27.0.1"
testImplementation "com.android.tools.lint:lint-tests:27.0.1"
}

View File

@ -0,0 +1,33 @@
package com.ichi2.anki.lint;
import com.android.tools.lint.detector.api.ApiKt;
import com.android.tools.lint.detector.api.Issue;
import com.ichi2.anki.lint.rules.DirectCalendarInstanceUsage;
import com.ichi2.anki.lint.rules.DirectSystemTimeInstantiation;
import com.ichi2.anki.lint.rules.DirectSystemCurrentTimeMillisUsage;
import com.ichi2.anki.lint.rules.DirectDateInstantiation;
import com.ichi2.anki.lint.rules.DirectGregorianInstantiation;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(DirectCalendarInstanceUsage.ISSUE);
issues.add(DirectSystemCurrentTimeMillisUsage.ISSUE);
issues.add(DirectDateInstantiation.ISSUE);
issues.add(DirectGregorianInstantiation.ISSUE);
issues.add(DirectSystemTimeInstantiation.ISSUE);
return issues;
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
}

View File

@ -0,0 +1,76 @@
package com.ichi2.anki.lint.rules;
import com.android.tools.lint.client.api.JavaEvaluator;
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.google.common.annotations.VisibleForTesting;
import com.ichi2.anki.lint.utils.Constants;
import com.ichi2.anki.lint.utils.LintUtils;
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 java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* This custom Lint rules will raise an error if a developer uses the {@link Calendar#getInstance()} method instead
* of using the Calendar provided by the new SystemTime class.
*/
public class DirectCalendarInstanceUsage extends Detector implements SourceCodeScanner {
@VisibleForTesting
static final String ID = "DirectCalendarInstanceUsage";
@VisibleForTesting
static final String DESCRIPTION = "Use SystemTime instead of directly creating Calendar instances";
private static final String EXPLANATION = "Manually creating Calendar instances means time cannot be controlled " +
"during testing. Calendar instances must be obtained through the collection's method `getTime()`";
private static Implementation implementation = new Implementation(DirectCalendarInstanceUsage.class, Scope.JAVA_FILE_SCOPE);
public static final Issue ISSUE = Issue.create(
ID,
DESCRIPTION,
EXPLANATION,
Constants.ANKI_TIME_CATEGORY,
Constants.ANKI_TIME_PRIORITY,
Severity.ERROR,
implementation
);
public DirectCalendarInstanceUsage() {
}
@Nullable
@Override
public List<String> getApplicableMethodNames() {
List<String> forbiddenMethods = new ArrayList<>();
forbiddenMethods.add("getInstance");
return forbiddenMethods;
}
@Override
public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
super.visitMethodCall(context, node, method);
JavaEvaluator evaluator = context.getEvaluator();
List<UClass> foundClasses = context.getUastFile().getClasses();
if (!LintUtils.isAnAllowedClass(foundClasses, "Time", "SystemTime") && evaluator.isMemberInClass(method, "java.util.Calendar")) {
context.report(
ISSUE,
context.getCallLocation(node, true, true),
DESCRIPTION
);
}
}
}

View File

@ -0,0 +1,74 @@
package com.ichi2.anki.lint.rules;
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.google.common.annotations.VisibleForTesting;
import com.ichi2.anki.lint.utils.Constants;
import com.ichi2.anki.lint.utils.LintUtils;
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 java.util.ArrayList;
import java.util.List;
/**
* This custom Lint rules will raise an error if a developer instantiates the {@link java.util.Date} class directly
* instead of using a {@link java.util.Date} provided through the new SystemTime class.
*/
public class DirectDateInstantiation extends Detector implements SourceCodeScanner {
@VisibleForTesting
static final String ID = "DirectDateInstantiation";
@VisibleForTesting
static final String DESCRIPTION = "Use SystemTime instead of directly instantiating Date";
private static final String EXPLANATION = "Creating Date instances directly means dates cannot be controlled during" +
" testing, so it is not allowed. Use the SystemTime class instead";
private static Implementation implementation = new Implementation(DirectDateInstantiation.class, Scope.JAVA_FILE_SCOPE);
public static final Issue ISSUE = Issue.create(
ID,
DESCRIPTION,
EXPLANATION,
Constants.ANKI_TIME_CATEGORY,
Constants.ANKI_TIME_PRIORITY,
Severity.ERROR,
implementation
);
public DirectDateInstantiation() {
}
@Nullable
@Override
public List<String> getApplicableConstructorTypes() {
List<String> forbiddenConstructors = new ArrayList<>();
forbiddenConstructors.add("java.util.Date");
return forbiddenConstructors;
}
@Override
public void visitConstructor(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod constructor) {
super.visitConstructor(context, node, constructor);
List<UClass> foundClasses = context.getUastFile().getClasses();
if (!LintUtils.isAnAllowedClass(foundClasses, "Time", "SystemTime")) {
context.report(
ISSUE,
node,
context.getLocation(node),
DESCRIPTION
);
}
}
}

View File

@ -0,0 +1,102 @@
package com.ichi2.anki.lint.rules;
import com.android.tools.lint.client.api.JavaEvaluator;
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.google.common.annotations.VisibleForTesting;
import com.ichi2.anki.lint.utils.Constants;
import com.ichi2.anki.lint.utils.LintUtils;
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 java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
/**
* This custom Lint rules will raise an error if a developer uses the {@link GregorianCalendar#from(ZonedDateTime)}
* method to obtain an instance instead of using the new SystemTime class.
*/
public class DirectGregorianInstantiation extends Detector implements SourceCodeScanner {
@VisibleForTesting
static final String ID = "DirectGregorianInstantiation";
@VisibleForTesting
static final String DESCRIPTION = "Use SystemTime instead of directly creating GregorianCalendar instances";
private static final String EXPLANATION = "Creating GregorianCalendar instances with from() is not allowed, as " +
"it prevents control of time during testing. Use the SystemTime class instead";
private static Implementation implementation = new Implementation(DirectGregorianInstantiation.class, Scope.JAVA_FILE_SCOPE);
public static final Issue ISSUE = Issue.create(
ID,
DESCRIPTION,
EXPLANATION,
Constants.ANKI_TIME_CATEGORY,
Constants.ANKI_TIME_PRIORITY,
Severity.ERROR,
implementation
);
public DirectGregorianInstantiation() {
}
@Nullable
@Override
public List<String> getApplicableMethodNames() {
List<String> forbiddenMethods = new ArrayList<>();
forbiddenMethods.add("from");
return forbiddenMethods;
}
@Nullable
@Override
public List<String> getApplicableConstructorTypes() {
List<String> forbiddenConstructors = new ArrayList<>();
forbiddenConstructors.add("java.util.GregorianCalendar");
return forbiddenConstructors;
}
@Override
public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
super.visitMethodCall(context, node, method);
JavaEvaluator evaluator = context.getEvaluator();
List<UClass> foundClasses = context.getUastFile().getClasses();
if (!LintUtils.isAnAllowedClass(foundClasses, "Time", "SystemTime") && evaluator.isMemberInClass(method, "java.util.GregorianCalendar")) {
context.report(
ISSUE,
context.getCallLocation(node, true, true),
DESCRIPTION
);
}
}
@Override
public void visitConstructor(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod constructor) {
super.visitConstructor(context, node, constructor);
List<UClass> foundClasses = context.getUastFile().getClasses();
if (!LintUtils.isAnAllowedClass(foundClasses, "Time", "SystemTime")) {
context.report(
ISSUE,
node,
context.getLocation(node),
DESCRIPTION
);
}
}
}

View File

@ -0,0 +1,75 @@
package com.ichi2.anki.lint.rules;
import com.android.tools.lint.client.api.JavaEvaluator;
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.google.common.annotations.VisibleForTesting;
import com.ichi2.anki.lint.utils.Constants;
import com.ichi2.anki.lint.utils.LintUtils;
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 java.util.ArrayList;
import java.util.List;
/**
* This custom Lint rules will raise an error if a developer uses the {@link System#currentTimeMillis()} method instead
* of using the time provided by the new SystemTime class.
*/
public class DirectSystemCurrentTimeMillisUsage extends Detector implements SourceCodeScanner {
@VisibleForTesting
static final String ID = "DirectSystemCurrentTimeMillisUsage";
@VisibleForTesting
static final String DESCRIPTION = "Use SystemTime instead of System.currentTimeMillis()";
private static final String EXPLANATION = "Using time directly means time values cannot be controlled during testing. " +
"Time values like System.currentTimeMillis() must be obtained through the SystemTime class";
private static Implementation implementation = new Implementation(DirectSystemCurrentTimeMillisUsage.class, Scope.JAVA_FILE_SCOPE);
public static final Issue ISSUE = Issue.create(
ID,
DESCRIPTION,
EXPLANATION,
Constants.ANKI_TIME_CATEGORY,
Constants.ANKI_TIME_PRIORITY,
Severity.ERROR,
implementation
);
public DirectSystemCurrentTimeMillisUsage() {
}
@Nullable
@Override
public List<String> getApplicableMethodNames() {
List<String> forbiddenSystemMethods = new ArrayList<>();
forbiddenSystemMethods.add("currentTimeMillis");
return forbiddenSystemMethods;
}
@Override
public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
super.visitMethodCall(context, node, method);
JavaEvaluator evaluator = context.getEvaluator();
List<UClass> foundClasses = context.getUastFile().getClasses();
if (!LintUtils.isAnAllowedClass(foundClasses, "Time", "SystemTime") && evaluator.isMemberInClass(method, "java.lang.System")) {
context.report(
ISSUE,
context.getCallLocation(node, true, true),
DESCRIPTION
);
}
}
}

View File

@ -0,0 +1,78 @@
package com.ichi2.anki.lint.rules;
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.google.common.annotations.VisibleForTesting;
import com.ichi2.anki.lint.utils.Constants;
import com.ichi2.anki.lint.utils.LintUtils;
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 java.util.ArrayList;
import java.util.List;
/**
* This custom Lint rules will raise an error if a developer instantiates the SystemTime class directly
* instead of using the Time class from a Collection.
* <br />
* NOTE: For future reference, if you plan on creating a Lint rule which looks for a constructor invocation, make sure
* that the target class has a constructor defined in its source code!
*/
public class DirectSystemTimeInstantiation extends Detector implements SourceCodeScanner {
@VisibleForTesting
static final String ID = "DirectSystemTimeInstantiation";
@VisibleForTesting
static final String DESCRIPTION = "Use Time instead of SystemTime";
private static final String EXPLANATION = "Creating SystemTime instances directly means time cannot be controlled during" +
" testing, so it is not allowed. Use the Time class instead";
private static Implementation implementation = new Implementation(DirectSystemTimeInstantiation.class, Scope.JAVA_FILE_SCOPE);
public static final Issue ISSUE = Issue.create(
ID,
DESCRIPTION,
EXPLANATION,
Constants.ANKI_TIME_CATEGORY,
Constants.ANKI_TIME_PRIORITY,
Severity.ERROR,
implementation
);
public DirectSystemTimeInstantiation() {
}
@Nullable
@Override
public List<String> getApplicableConstructorTypes() {
List<String> forbiddenConstructors = new ArrayList<>();
forbiddenConstructors.add("com.ichi2.libanki.utils.SystemTime");
return forbiddenConstructors;
}
@Override
public void visitConstructor(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod constructor) {
super.visitConstructor(context, node, constructor);
List<UClass> foundClasses = context.getUastFile().getClasses();
if (!LintUtils.isAnAllowedClass(foundClasses, "Storage", "CollectionHelper")) {
context.report(
ISSUE,
node,
context.getLocation(node),
DESCRIPTION
);
}
}
}

View File

@ -0,0 +1,22 @@
package com.ichi2.anki.lint.utils;
import com.android.tools.lint.detector.api.Category;
import static com.android.tools.lint.detector.api.Category.create;
/**
* Hold some constants applicable to all lint issues.
*/
public class Constants {
/**
* A special {@link Category} which groups the Lint issues related to the usage of the new SystemTime class as a
* sub category for {@link Category#CORRECTNESS}.
*/
public static final Category ANKI_TIME_CATEGORY = create(Category.CORRECTNESS, "AnkiTime", 10);
/**
* The priority for the Lint issues used by all rules related to the restrictions introduced by SystemTime.
*/
public static final int ANKI_TIME_PRIORITY = 10;
}

View File

@ -0,0 +1,35 @@
package com.ichi2.anki.lint.utils;
import org.jetbrains.uast.UClass;
import java.util.List;
public class LintUtils {
private LintUtils() {
// no instantiation
}
/**
* A helper method to check for special classes(Time and SystemTime) where the rules related to time apis shouldn't
* be applied.
*
* @param classes the list of classes to look through
* @param allowedClasses the list of classes where the checks should be ignored
* @return true if this is a class where the checks should not be applied, false otherwise
*/
public static boolean isAnAllowedClass(List<UClass> classes, String... allowedClasses) {
boolean isInAllowedClass = false;
for (int i = 0; i < classes.size(); i++) {
final String className = classes.get(i).getName();
for (String allowedClass: allowedClasses) {
if (className.equals(allowedClass)) {
isInAllowedClass = true;
break;
}
}
}
return isInAllowedClass;
}
}

View File

@ -0,0 +1 @@
com.ichi2.anki.lint.IssueRegistry

View File

@ -0,0 +1,52 @@
package com.ichi2.anki.lint.rules;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertTrue;
public class DirectCalendarInstanceUsageTest {
private final String stubCalendar = " \n" +
"package java.util; \n" +
" \n" +
"public class Calendar { \n" +
" \n" +
" public static Calendar getInstance() { \n" +
" return null; \n" +
" } \n" +
"} \n";
private final String javaFileToBeTested = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.util.Calendar; \n" +
" \n" +
"public class TestJavaClass { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" Calendar c = Calendar.getInstance(); \n" +
" c.clear(); \n" +
" } \n" +
"} \n";
@Test
public void showsErrorForInvalidUsage() {
lint()
.allowMissingSdk()
.allowCompilationErrors()
.files(create(stubCalendar), create(javaFileToBeTested))
.issues(DirectCalendarInstanceUsage.ISSUE)
.run()
.expectErrorCount(1)
.check(output -> {
assertTrue(output.contains(DirectCalendarInstanceUsage.ID));
assertTrue(output.contains(DirectCalendarInstanceUsage.DESCRIPTION));
});
}
}

View File

@ -0,0 +1,47 @@
package com.ichi2.anki.lint.rules;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertTrue;
public class DirectDateInstantiationTest {
private final String stubDate = " \n" +
"package java.util; \n" +
" \n" +
"public class Date { \n" +
" \n" +
" public Date() { \n" +
" \n" +
" } \n" +
"} \n";
private final String javaFileToBeTested = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.util.Date; \n" +
" \n" +
"public class TestJavaClass { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" Date d = new Date(); \n" +
" } \n" +
"} \n";
@Test
public void showsErrorsForInvalidUsage() {
lint().
allowMissingSdk().
allowCompilationErrors()
.files(create(stubDate), create(javaFileToBeTested))
.issues(DirectDateInstantiation.ISSUE)
.run()
.expectErrorCount(1)
.check(output -> {
assertTrue(output.contains(DirectDateInstantiation.ID));
assertTrue(output.contains(DirectDateInstantiation.DESCRIPTION));
});
}
}

View File

@ -0,0 +1,133 @@
package com.ichi2.anki.lint.rules;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertTrue;
public class DirectGregorianInstantiationTest {
private final String stubZoned = " \n" +
"package java.time; \n" +
" \n" +
"public class ZonedDateTime { \n" +
" \n" +
" \n" +
" public ZonedDateTime() { \n" +
" \n" +
" } \n" +
"} \n";
private final String stubGregorian = " \n" +
"package java.util; \n" +
" \n" +
"import java.time.ZonedDateTime; \n" +
" \n" +
"public class GregorianCalendar { \n" +
" \n" +
" public GregorianCalendar() { \n" +
" } \n" +
" \n" +
" \n" +
" public static GregorianCalendar from(ZonedDateTime z) { \n" +
" return null; \n" +
" } \n" +
"} \n";
private final String javaFileWithFromCall = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.util.GregorianCalendar; \n" +
" \n" +
"public class TestJavaClass { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" GregorianCalendar gc = GregorianCalendar.from(null); \n" +
" } \n" +
"} \n";
private final String javaFileWithConstructorInvocation = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.util.GregorianCalendar; \n" +
" \n" +
"public class TestJavaClass { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" GregorianCalendar gc = new GregorianCalendar(); \n" +
" } \n" +
"} \n";
private final String javaFileWithTime = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.util.GregorianCalendar; \n" +
" \n" +
"public class Time { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" GregorianCalendar gc = new GregorianCalendar(); \n" +
" } \n" +
"} \n";
private final String javaFileWithSystemTime = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.util.GregorianCalendar; \n" +
" \n" +
"public class SystemTime { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" GregorianCalendar gc = new GregorianCalendar(); \n" +
" } \n" +
"} \n";
@Test
public void showsErrorForInvalidUsage() {
lint().
allowMissingSdk().
allowCompilationErrors()
.files(create(stubZoned), create(stubGregorian), create(javaFileWithFromCall))
.issues(DirectGregorianInstantiation.ISSUE)
.run()
.expectErrorCount(1)
.check(output -> {
assertTrue(output.contains(DirectGregorianInstantiation.ID));
assertTrue(output.contains(DirectGregorianInstantiation.DESCRIPTION));
});
lint().
allowMissingSdk().
allowCompilationErrors()
.files(create(stubZoned), create(stubGregorian), create(javaFileWithConstructorInvocation))
.issues(DirectGregorianInstantiation.ISSUE)
.run()
.expectErrorCount(1)
.check(output -> {
assertTrue(output.contains(DirectGregorianInstantiation.ID));
assertTrue(output.contains(DirectGregorianInstantiation.DESCRIPTION));
});
}
@Test
public void doesNotShowErrorsWhenUsedInTime() {
lint().
allowMissingSdk()
.allowCompilationErrors()
.files(create(stubGregorian), create(javaFileWithTime))
.issues(DirectSystemTimeInstantiation.ISSUE)
.run()
.expectClean();
}
@Test
public void doesNotShowErrorsWhenUsedInSystemTime() {
lint().
allowMissingSdk()
.allowCompilationErrors()
.files(create(stubGregorian), create(javaFileWithSystemTime))
.issues(DirectSystemTimeInstantiation.ISSUE)
.run()
.expectClean();
}
}

View File

@ -0,0 +1,48 @@
package com.ichi2.anki.lint.rules;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertTrue;
public class DirectSystemCurrentTimeMillisUsageTest {
private final String stubSystem = " \n" +
"package java.lang; \n" +
" \n" +
"public class System { \n" +
" \n" +
" public static long currentTimeMillis() { \n" +
" return 1L; \n" +
" } \n" +
"} \n";
private final String javaFileToBeTested = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import java.lang.System; \n" +
" \n" +
"public class TestJavaClass { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" long time = System.currentTimeMillis(); \n" +
" } \n" +
"} \n";
@Test
public void showsErrorsForInvalidUsage() {
lint().
allowMissingSdk().
allowCompilationErrors()
.files(create(stubSystem), create(javaFileToBeTested))
.issues(DirectSystemCurrentTimeMillisUsage.ISSUE)
.run()
.expectErrorCount(1)
.check(output -> {
assertTrue(output.contains(DirectSystemCurrentTimeMillisUsage.ID));
assertTrue(output.contains(DirectSystemCurrentTimeMillisUsage.DESCRIPTION));
});
}
}

View File

@ -0,0 +1,98 @@
package com.ichi2.anki.lint.rules;
import org.junit.Test;
import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertTrue;
public class DirectSystemTimeInstantiationTest {
private final String stubTime = " \n" +
"package com.ichi2.libanki.utils; \n" +
" \n" +
"public abstract class Time { \n" +
" \n" +
"} \n";
private final String stubSystemTime = " \n" +
"package com.ichi2.libanki.utils; \n" +
" \n" +
"public class SystemTime extends Time { \n" +
" \n" +
" public SystemTime() { \n" +
" } \n" +
" \n" +
"} \n";
private final String javaFileToBeTested = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import com.ichi2.libanki.utils.SystemTime; \n" +
" \n" +
"public class TestJavaClass { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" SystemTime st = new SystemTime(); \n" +
" } \n" +
"} \n";
private final String javaFileWithStorage = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import com.ichi2.libanki.utils.SystemTime; \n" +
" \n" +
"public class Storage { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" SystemTime st = new SystemTime(); \n" +
" } \n" +
"} \n";
private final String javaFileWithCollectionHelper = " \n" +
"package com.ichi2.anki.lint.rules; \n" +
" \n" +
"import com.ichi2.libanki.utils.SystemTime; \n" +
" \n" +
"public class CollectionHelper { \n" +
" \n" +
" public static void main(String[] args) { \n" +
" SystemTime st = new SystemTime(); \n" +
" } \n" +
"} \n";
@Test
public void showsErrorsForInvalidUsage() {
lint()
.allowMissingSdk()
.allowCompilationErrors()
.files(create(stubTime), create(stubSystemTime), create(javaFileToBeTested))
.issues(DirectSystemTimeInstantiation.ISSUE)
.run()
.expectErrorCount(1)
.check(output -> {
assertTrue(output.contains(DirectSystemTimeInstantiation.ID));
assertTrue(output.contains(DirectSystemTimeInstantiation.DESCRIPTION));
});
}
@Test
public void doesNotShowErrorsWhenUsedInStorage() {
lint().
allowMissingSdk()
.allowCompilationErrors()
.files(create(stubTime), create(stubSystemTime), create(javaFileWithStorage))
.issues(DirectSystemTimeInstantiation.ISSUE)
.run()
.expectClean();
}
@Test
public void doesNotShowErrorsWhenUsedInCollectionHelper() {
lint().
allowMissingSdk()
.allowCompilationErrors()
.files(create(stubTime), create(stubSystemTime), create(javaFileWithCollectionHelper))
.issues(DirectSystemTimeInstantiation.ISSUE)
.run()
.expectClean();
}
}

View File

@ -1,2 +1,3 @@
include ':lint-rules'
include ':api'
include ':AnkiDroid'