自定义SonarQube扫描Java Rule


在介绍如何自定义规则之前,先介绍一下这几个产品:

  • SonarQube 代码质量管理平台
  • PMD 源代码分析器
  • FindBugs Java源代码分析器,查找代码Bug
  • Sonar-PMD Sonar市场提供的PMD插件开源项目
  • P3C Alibaba的代码规范,提供了IDEA、Eclipse和PMD三个版本

虽然开源平台已经帮我们提供众多的Java安全和代码Bug的校验规则(如:P3C、PMD、FindBugs、Sonar Way),但是这些可能仍然无法满足公司对于代码的校验,例如:公司规定不得以老板的姓名作为变量名(类似的需求);因此我们不得不按照老板的规定,来扫描同事们的代码。如何来定义一个自己的Java规范呢,Sonar文档中已有说明:Sonar Doc

按照文档说明,我们应该从头开始开发一个Sonar插件,这显然是非常耗时的,因此我们直接Fork Sonar-PMD 进行修改只需要集成自定义的规则即可。接下来我们就直接进入实操环节,我们以不能将变量定义为password为例。因设计规则需要XPath,请自行学习。

开发一条PMD的规则步骤如下:

1. 设计规则

因为我们使用的源码分析器是PMD,因此我们需要使用PMD的规则设计方式,参考文档:PMD 规则设计 PMD的规则设计器可以帮助我们辅助分析AST 可视化的规则设计器

2. 编写规则

将设计器里的规则copy出来,我们设计为Xpath或使用Java代码分析AST。 我们以不能将变量名称定义为password为例:

  • Xpath定义方式
//VariableDeclaratorId[@Image = "password"]
  • Java代码定义方式
public class DontDefinePasswordRule extends AbstractJavaRule{

	@Override
	public Object visit(ASTVariableDeclaratorId node, Object data) {
		if ("password".equalsIgnoreCase(node.getName()) ) {
            // reports a violation at the position of the node
            // the "data" parameter is a context object handed to by your rule
            // the message for the violation is the message defined in the rule declaration XML element
            addViolation(data, node);
        }
		
		return super.visit(node, data);
	}
	
}

3. 配置Rule

我们可以根据规则的分类,为不同的规则建立不同的规则文件,例如示例的这个配置文件:src\main\resources\rulesets\java\myrule-other.xml

  • Java代码配置方式
<rule name="DontDefinePasswordRule" language="java"
        message="Do not define a variable named password"
        class="com.myrule.pmd.rule.DontDefinePasswordRule">
        <description>Do not define a variable named password</description>
        <priority>1</priority>
        <example>
<![CDATA[
    private String pwd = "xxxx";
]]>
      </example>
    </rule>
  • XPath配置方式
 <rule name="AbstractClassWithoutAnyMethod"
          language="java"
          since="4.2"
          class="net.sourceforge.pmd.lang.rule.XPathRule"
          typeResolution="true"
          message="No abstract method which means that the keyword is most likely used to prevent instantiation. Use a private or protected constructor instead."
          externalInfoUrl="https://pmd.github.io/pmd-6.31.0/pmd_rules_java_design.html#abstractclasswithoutanymethod">
        <description>
If an abstract class does not provides any methods, it may be acting as a simple data container
that is not meant to be instantiated. In this case, it is probably better to use a private or
protected constructor in order to prevent instantiation than make the class misleadingly abstract.
        </description>
        <priority>1</priority>
        <properties>
            <property name="version" value="2.0"/>
            <property name="xpath">
                <value>
<![CDATA[
//ClassOrInterfaceDeclaration
    [@Abstract = true()]
    [not(./ClassOrInterfaceBody/*/ConstructorDeclaration)]
    [not(./ClassOrInterfaceBody/*/MethodDeclaration)]
    [not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('com.google.auto.value.AutoValue')])]
]]>
                </value>
            </property>
        </properties>
        <example>
<![CDATA[
public abstract class Example {
    String field;
    int otherField;
}
]]>
        </example>
    </rule>

4. 配置规则查找的文件

配置文件地址:src\main\resources\org\sonar\plugins\pmd\rules-myrule.xml

<rule key="DontDefinePasswordRule">
        <priority>MAJOR</priority>
        <configKey><![CDATA[rulesets/java/myrule-other.xml/DontDefinePasswordRule]]></configKey>
</rule>

5. 配置规则属性

配置文件地址:src\main\resources\org\sonar\l10n\pmd.properties

rule.pmd.DontDefinePasswordRule.name=[myrule]Do not define a variable named password.

6. 配置规则示例

配置文件地址:src\main\resources\org\sonar\l10n\pmd\rules\pmd-myrule 每个规则一个html文件

Do not define a variable named password. Examples:
<pre>
    private String pwd;
</pre>

7. 配置PMD model文件

配置文件地址:src\main\resources\com\sonar\sqale\pmd-model.xml 在最后的Myrule PMD中添加clc

<chc>
    <rule-repo>pmd</rule-repo>
    <rule-key>DontDefinePasswordRule</rule-key>
    <prop>
        <key>remediationFunction</key>
        <txt>CONSTANT_ISSUE</txt>
    </prop>
    <prop>
        <key>offset</key>
        <val>2</val>
        <txt>min</txt>
    </prop>
</chc>