如何在Android Studio中正确实现和测试自定义Lint规则?
我遵循并执行自定义Lint规则。基本上我所做的是:如何在Android Studio中正确实现和测试自定义Lint规则?,android,lint,android-lint,Android,Lint,Android Lint,我遵循并执行自定义Lint规则。基本上我所做的是: 在Android Studio中创建一个新的Android项目 为步骤1中创建的项目创建java模块 在模块的build.gradle上,导入Lint API依赖项 创建问题和问题注册和客户检测器 参考模块的build.gradle上的IssueRegistry 创建单元测试 我的问题是,在执行JUnits期间,我总是收到“没有警告”。当我调试测试时,我可以看到我的自定义检测器没有被调用,我做错了什么 Strings.java public c
build.gradle
上,导入Lint API依赖项李>
问题
和问题注册
和客户检测器
李>
build.gradle
上的IssueRegistry
李>
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
@Override
public boolean appliesToResourceRefs() {
return false;
}
@Override
public boolean appliesTo(Context context, File file) {
return true;
}
@Override
@Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
@Override
public List<String> getApplicableMethodNames() {
return null;
}
@Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
@Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
@Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
@Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
@Override
protected Detector getDetector() {
return new VariableNameDetector();
}
@Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
Issues.java
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
@Override
public boolean appliesToResourceRefs() {
return false;
}
@Override
public boolean appliesTo(Context context, File file) {
return true;
}
@Override
@Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
@Override
public List<String> getApplicableMethodNames() {
return null;
}
@Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
@Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
@Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
@Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
@Override
protected Detector getDetector() {
return new VariableNameDetector();
}
@Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
issueRegistry.java
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
@Override
public boolean appliesToResourceRefs() {
return false;
}
@Override
public boolean appliesTo(Context context, File file) {
return true;
}
@Override
@Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
@Override
public List<String> getApplicableMethodNames() {
return null;
}
@Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
@Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
@Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
@Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
@Override
protected Detector getDetector() {
return new VariableNameDetector();
}
@Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
TestVariableNameDetector.java
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
@Override
public boolean appliesToResourceRefs() {
return false;
}
@Override
public boolean appliesTo(Context context, File file) {
return true;
}
@Override
@Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
@Override
public List<String> getApplicableMethodNames() {
return null;
}
@Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
@Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
@Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
@Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
@Override
protected Detector getDetector() {
return new VariableNameDetector();
}
@Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
private static final String ARG\u DEFAULT\u LINT\u SUCCESS\u LOG=“无警告。”;
@凌驾
受保护检测器getDetector(){
返回新的VariableNameDetector();
}
@凌驾
受保护的列表getIssues(){
返回集合。单件列表(问题。问题001);
}
带有长度等于的变量的公共无效测试文件抛出异常{
资产质量(
ARG\u默认值\u LINT\u成功日志,
lintProject(java(“assets/Test.java”,“公共类测试{publicstringsamplevariable;}”))
);
}
带有变量的公共无效测试文件抛出异常{
资产质量(
ARG\u默认值\u LINT\u成功日志,
lintProject(java(“assets/Test3.java”,“公共类测试{publicstringa;bnvhgvhj}”))
);
}
}
注意:在Java的模块上,我无法访问
资产
或资源
文件夹,这就是我创建字符串.Java
并使用Java(to,source)的原因
在我的单元测试中-我假设这个java
方法与我在问题顶部引用的教程链接中的xml
相同。我不确定如何使用AST Api,但是我个人使用的是Psi
,这是我的一个lint检查,对我有效
public final class RxJava2MethodCheckReturnValueDetector extends Detector implements Detector.JavaPsiScanner {
static final Issue ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE =
Issue.create("MethodMissingCheckReturnValue", "Method is missing the @CheckReturnValue annotation",
"Methods returning RxJava Reactive Types should be annotated with the @CheckReturnValue annotation.",
MESSAGES, 8, WARNING,
new Implementation(RxJava2MethodCheckReturnValueDetector.class, EnumSet.of(JAVA_FILE, TEST_SOURCES)));
@Override public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
return Collections.<Class<? extends PsiElement>>singletonList(PsiMethod.class);
}
@Override public JavaElementVisitor createPsiVisitor(@NonNull final JavaContext context) {
return new CheckReturnValueVisitor(context);
}
static class CheckReturnValueVisitor extends JavaElementVisitor {
private final JavaContext context;
CheckReturnValueVisitor(final JavaContext context) {
this.context = context;
}
@Override public void visitMethod(final PsiMethod method) {
final PsiType returnType = method.getReturnType();
if (returnType != null && Utils.isRxJava2TypeThatRequiresCheckReturnValueAnnotation(returnType)) {
final PsiAnnotation[] annotations = method.getModifierList().getAnnotations();
for (final PsiAnnotation annotation : annotations) {
if ("io.reactivex.annotations.CheckReturnValue".equals(annotation.getQualifiedName())) {
return;
}
}
final boolean isMethodMissingCheckReturnValueSuppressed = context.getDriver().isSuppressed(context, ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, method);
if (!isMethodMissingCheckReturnValueSuppressed) {
context.report(ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, context.getLocation(method.getNameIdentifier()), "Method should have @CheckReturnValue annotation");
}
}
}
}
}
public final类RxJava2MethodCheckReturnValueDetector扩展Detector实现Detector.JavaPsiScanner{
静态最终发布发布方法缺失检查返回值=
问题.创建(“MethodMissingCheckReturnValue”,“方法缺少@CheckReturnValue注释”,
“返回RxJava被动类型的方法应使用@CheckReturnValue注释进行注释。”,
信息,8,警告,
新的实现(RxJava2MethodCheckReturnValueDetector.class,EnumSet.of(JAVA_文件,TEST_源代码));
@Override public List事实证明,在我的例子中,问题在于JUnit本身。我认为我尝试模拟文件的方式是错误的。下面的文本是我创建的部分内容,目的是记录我从该API学到的知识,并回答标题中的问题:
创造
创建一个新的Android项目
创建一个新的Java库模块——定制Lint规则一旦准备好就打包到.jar库中,因此使用它们实现它们的最简单方法是在Java模块库中
在模块的build.gradle上
:
- 将目标和源代码兼容性添加到Java1.7中
- 添加lint api、lint检查和测试依赖项的依赖项
- 添加包含两个属性的jar打包任务:
Manifest Version
和Lint Registry
,将第一个属性设置为1.0,将第二个属性设置为稍后将包含问题目录的类的完整路径
- 添加默认的tasl
assembly
- [可选]:添加一个任务,将生成的.jar复制到
~/.android/lint
检查REF001并选择最适合您需要的检测器,根据它创建并实现一个类来完成检测器的角色
仍然基于REF0001选择的文件,创建并实现一个Checker类,稍后在Detector的createJavaVisitor()方法中引用它;
- 为了SRP,不要将检查器放在探测器类的同一文件中
将生成的.jar文件从build/lib
复制到~/.android/lint
-如果您在build.gradle
上添加了一个任务,您可以跳过此步骤
重新启动计算机-一旦创建并移动到~/.android/lint
,下次程序启动时,lint应该读取自定义规则。为了在android Studio中设置警报框,在/gradlew检查时,使缓存无效并重新启动IDE就足够了,但在lint报告中捕获自定义规则
,可能需要重新启动计算机
测试探测器和检查器
测试自定义规则不是一项容易的任务-主要是因为缺少官方API的文档。本节将介绍两种处理方法。本项目的主要目标是创建将针对真实文件运行的自定义规则,因此,测试这些规则需要测试文件。它们可以放在e> 来自Lint Java库模块的src/test/resources
文件夹
方法01:侵权测试
build.gradle
- Android Studio有一个已知的bug,它阻止它查看
文件夹中的文件,这些文件是解决这个问题的方法src/test/resources
应该返回所有将作为测试主题的问题。一个很好的方法是从问题注册表获取它们EnhancedLintDetectorTest.java