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:
parent
7095a48c01
commit
de3f38ee37
@ -274,4 +274,7 @@ dependencies {
|
|||||||
|
|
||||||
//For AnkiDroid JS API Versioning
|
//For AnkiDroid JS API Versioning
|
||||||
implementation "com.github.zafarkhaja:java-semver:0.9.0"
|
implementation "com.github.zafarkhaja:java-semver:0.9.0"
|
||||||
|
|
||||||
|
// Adds the custom lint rules
|
||||||
|
lintChecks project(":lint-rules")
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,11 @@ import java.util.Date;
|
|||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
public class SystemTime extends Time {
|
public class SystemTime extends Time {
|
||||||
|
|
||||||
|
public SystemTime() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long intTimeMS() {
|
public long intTimeMS() {
|
||||||
return System.currentTimeMillis();
|
return System.currentTimeMillis();
|
||||||
|
@ -52,6 +52,8 @@ dependencies {
|
|||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
testImplementation 'org.junit.vintage:junit-vintage-engine:5.6.2'
|
testImplementation 'org.junit.vintage:junit-vintage-engine:5.6.2'
|
||||||
testImplementation 'org.robolectric:robolectric:4.3.1'
|
testImplementation 'org.robolectric:robolectric:4.3.1'
|
||||||
|
|
||||||
|
lintChecks project(":lint-rules")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Borrowed from here:
|
// Borrowed from here:
|
||||||
|
1
lint-rules/.gitignore
vendored
Normal file
1
lint-rules/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
20
lint-rules/build.gradle
Normal file
20
lint-rules/build.gradle
Normal 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"
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
com.ichi2.anki.lint.IssueRegistry
|
@ -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));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
|
include ':lint-rules'
|
||||||
include ':api'
|
include ':api'
|
||||||
include ':AnkiDroid'
|
include ':AnkiDroid'
|
||||||
|
Loading…
Reference in New Issue
Block a user