Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/228.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在Android Studio中正确实现和测试自定义Lint规则?_Android_Lint_Android Lint - Fatal编程技术网

如何在Android Studio中正确实现和测试自定义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

我遵循并执行自定义Lint规则。基本上我所做的是:

  • 在Android Studio中创建一个新的Android项目
  • 为步骤1中创建的项目创建java模块
  • 在模块的
    build.gradle
    上,导入Lint API依赖项
  • 创建
    问题
    问题注册
    客户检测器
  • 参考模块的
    build.gradle
    上的
    IssueRegistry
  • 创建单元测试 我的问题是,在执行JUnits期间,我总是收到“没有警告”。当我调试测试时,我可以看到我的自定义检测器没有被调用,我做错了什么

    Strings.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}"))
            );
        }
    }
    
    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
      应该返回所有将作为测试主题的问题。一个很好的方法是从问题注册表获取它们