自动化工具 Sonar Java 规则插件开发 (基于阿里开发手册)

CC · 2017年03月25日 · 最后由 兰陵小旋风 回复于 2020年05月08日 · 3770 次阅读

引言

最近在做 Sonar 静态代码扫描管理,以此顺手接了 Sonar 的插件开发,基于阿里开发手册进行开发,在整体开发过程中,其中还是遇到不少坑位,也以此给大家做相应借鉴
官网 Demo 演示插件开发地址:
https://docs.sonarqube.org/display/PLUG/Writing+Custom+Java+Rules+101
基于官网的我暂时不多说,基础框架按照官网的范例进行搭建即可

# 开源地址:
https://github.com/tigerge000/sonar-java-custom-rules.git

sonar 常用方法说明

范例

需求:【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。
实现:
AbstractClassNameCheck

package org.finger.java.rule.checks.namerules;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;

/**
 * 抽象类命名检查
 * 抽象类命名使用 Abstract 或 Base 开头
 * Created by 古月随笔 on 2017/3/17.
 */
@Rule(key = "AbstractClassNameCheck")
public class AbstractClassNameCheck extends BaseTreeVisitor implements JavaFileScanner{

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractClassNameCheck.class);

    private JavaFileScannerContext context;

    @Override
    public void scanFile(JavaFileScannerContext context) {
        this.context = context;
        scan(context.getTree());
    }

    @Override
    public void visitClass(ClassTree tree) {
        String className = tree.simpleName().name();

        LOGGER.info(className + "<<>>" + tree.symbol().isAbstract());


        if(tree.symbol().isAbstract()){
            //判断名称是否以Abstract 或 Base 开头

            String abName = "Abstract";
            String bsName = "Base";
            //判断类名如果小于Abstract 或 Base
            if (className.length() < abName.length() || className.length() < bsName.length()) {
                context.reportIssue(this, tree, "The Name Of Abstract Class should use Abstract or Base first");
            } else {
                //判断是否存在 Abstract 或 Base
                if (!className.contains(abName)) {
                    if (!className.contains(bsName)) {
                        context.reportIssue(this, tree, "The Name Of Abstract Class should use Abstract or Base first");
                    } else {
                        if (className.indexOf(bsName) != 0) {
                            context.reportIssue(this, tree, "The Name Of Abstract Class should use Abstract or Base first");
                        }
                    }
                } else {
                    if (className.indexOf(abName) != 0) {
                        context.reportIssue(this, tree, "The Name Of Abstract Class should use Abstract or Base first");
                    }
                }
            }
        }
        super.visitClass(tree);
    }

}

resources 目录下添加目录:org.sonar.l10n.java.rules.squid(这命名主要是由于 sonar 源码中固定写死的)
AbstractClassNameCheck.html

<p>AbstractClassNameCheck Check</p>
<h2>Noncompliant Code Example</h2>
<pre>
    public abstract class HiClass {// Noncompliant
    }

    public abstract class MyNameIsAbstract {// Noncompliant
    }
</pre>
<h2>Compliant Solution</h2>
<pre>
    public abstract class AbstractMysql {
    }

    public abstract class BaseMysql {
    }
</pre>

AbstractClassNameCheck.json

{
  "title": "The Name Of Abstract Class should use Abstract or Base first",
  "status": "ready",
  "remediation": {
    "func": "Constant\/Issue",
    "constantCost": "5min"
  },
  "tags": [
    "bug",
    "pitfall"
  ],
  "defaultSeverity": "CRITICAL"
}

AbstractClassNameCheck_java.json(为什么有这个文件,主要是由于进行单测的时候,sonar 源码内容写死是 xxxx_java.json 命名)

{
  "title": "The Name Of Abstract Class should use Abstract or Base first",
  "status": "ready",
  "remediation": {
    "func": "Constant\/Issue",
    "constantCost": "5min"
  },
  "tags": [
    "bug",
    "pitfall"
  ],
  "defaultSeverity": "CRITICAL"
}

单测:
AbstractClassNameCheckTest

package org.finger.java.rule.checks.namerules;

import org.junit.Test;
import org.sonar.java.checks.verifier.JavaCheckVerifier;

/**
 * Created by huqingen on 2017/3/17.
 */
public class AbstractClassNameCheckTest {
    @Test
    public void test() {
        JavaCheckVerifier.verify("src/test/files/HiClass.java", new AbstractClassNameCheck());
    }

    @Test
    public void test1() {
        JavaCheckVerifier.verify("src/test/files/MyNameIsAbstract.java", new AbstractClassNameCheck());
    }

    @Test
    public void test2() {
        JavaCheckVerifier.verify("src/test/files/BaseMysql.java", new AbstractClassNameCheck());
    }
    @Test
    public void test3() {
        JavaCheckVerifier.verify("src/test/files/AbstractMysql.java", new AbstractClassNameCheck());
    }
}

开发目录结构:

PS:
1.由于编辑的时候经常出现 crash 问题,暂时先写到这,😓,真不是故意
2.编写 sonar 规则的时候,给个建议,采用 Debug 方式调试整个 tree 里面的内容,针对性的编写自己想要的规则,sonar 在这块扫描基础方法做的还是很棒的

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 22 条回复 时间 点赞

我们一直在做这方面的规则开发,赞楼主分享的精神,只不过在静态代码检查这块测试人员有能力写自定义规则的很少,写出来不误报的更少

CC #2 · 2017年03月26日 Author

#1 楼 @simple 😁😁多写写,其实就那样,主要想自己逼下自己;一般出现误报可能概率比较低,如果自己研究出这个规律,一般出现多的是异常,初期我这边有时候没考虑到 interface 情况;

—— 来自 TesterHome 官方 安卓客户端

你好,我最近也在试着自定义规则,能否获得你自己开发的这个规则的源码,还有 sonar-plugin-api 的文档在哪儿可以找到,希望你能给一些指导,谢谢。

CC #4 · 2017年06月02日 Author
冯澍雨 回复

https://docs.sonarqube.org/display/PLUG/Writing+Custom+Java+Rules+101
你可以按照这个模版先写一遍,把 Demo 好后,你这边可以 采用 Debug 设断点方式,慢慢的看调试信息,调试信息会显示该方法对应的调用;这种基本上还是靠自己一步一步的调出来的

CC 回复

我看你的这种方法与官方的不一样,并且感觉比官方的要清晰。

CC Sonar 平台搭建及 Sonar 自定义规则打包部署篇 中提及了此贴 07月14日 15:49

楼主这个 AbstractClassNameCheck 文件里面的结构体就不符合我的个人规范呢,嵌套太多层 if else 了,反向判定看清不是更清晰么😂

楼主不要怪我无聊,中午的确很无聊

public void visitClass(ClassTree tree) {
    String className = tree.simpleName().name();

    LOGGER.info(className + "<<>>" + tree.symbol().isAbstract());

    if(!tree.symbol().isAbstract()){
        super.visitClass(tree);
        return ;
    }

    //判断名称是否以Abstract 或 Base 开头

    String abName = "Abstract";
    String bsName = "Base";

    if (!className.startsWith(abName) || !className.startsWith(bsName)) {
        context.reportIssue(this, tree, "The Name Of Abstract Class should use Abstract or Base first");
    }

    super.visitClass(tree);
}
CC #9 · 2017年08月04日 Author

#8 楼 @fudax 不错呀,有时候会出现思维定式。只不过我好久没弄这个规则了,目前公司还没关注到这块

—— 来自 TesterHome 官方 安卓客户端

org.sonar.java.checks.verifier.JavaCheckVerifier; 这个类是在哪个包下的呀?

CC Sonar 平台搭建及 Sonar 自定义规则打包部署篇 中提及了此贴 04月20日 19:31

MyJavaRulesDefinition 这个类中,有导入一个包 import org.sonar.plugins.java.Java;然后使用到包中的 Java.KEY,为什么我这边死活导入不了该包? @hu_qingen

CC #13 · 2018年05月15日 Author
very 回复

你可以试下手动下载这个包看下,maven 下载有时候就是这么奇葩

CC #14 · 2018年05月15日 Author

其实所有的规则可以参考 sonar 原生的 jar 包,看下他们的语法是怎么写的

CC 回复

嗯,已经解决了,https://www.jianshu.com/p/eadf42a7091a

楼主,阿里的 java 规则 参考在 Github 上 提交一份吧,这样可以让更多人参与编写这个阿里的 java 规范插件。我有想法参与一起搞下这个 ali 的 java 规范 自定义规则的编写。

mumu 回复

到时候可以联系下我吗 1181476175@qq.com

仅楼主可见

楼主,请教个问题。我在单元测试的时候有些测试总是报错,找不出是什么原因导致,想请教一下 java.lang.IllegalStateException:At least one issue expected

CC #20 · 2019年02月21日 Author

你检测的文件,是不是有刚好不符合你写的检测规则,所以报错,那算是正常报错

仅楼主可见
仅楼主可见
仅楼主可见
仅楼主可见
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册