扩展Alibaba P3C 实现自定义代码规范检查
P 3 C (源码地址: https://github.com/alibaba/p3c )
- 我是基于网友的改版在进行三次修改的:
P 3 C 一款代码规范的检查工具,有对应的 ide 插件,能在编码过程中对设置的规则进行提示,可以针对公司编码规范对它原来基础上做了进一步的拓展;
P 3 C 主要包括 3 部分:
- PMD 实现 (p3c-pmd):使用 PMD 来实现代码规范检查
- Intellij IDEA 插件
- Eclipse 插件
PMD
P 3 c 使用了 PMD。PMD 是一款静态代码扫描工具,该工具可以做到检查 Java 代码中是否含有未使用的变量、是否含有空的抓取块、是否含有不必要的对象等。PMD 使用 JavaCC 生成解析器来解析源代码并生成 AST (抽象语法树),通过对 AST 的检查可以直接从源代码文本层面来对代码进行检查,在 PMD 内部称为规则。即是否符合规则指的是,穷举源码各种可能的写法,然后在 AST 上检查是否出现。而规则的实现,重点便在对 AST 的处理上。
pmd-bin-6.55.0【PMD 可执行版本】 ( https://pmd.github.io/ )
解压后会发现主要有两个目录:
bindesigner.bat:【界面工具,能将 java 源代码转化为 AST(抽象语法树),个人推荐使用】bgastviewer.bat:【界面工具,与designer.bat功能相似】cpd.bat:【用来查找重复代码的工具,命令行版】cpdgui.bat:【用来查找重复代码的工具,GUI 版】pmd.bat:【Window 平台下运行 PMD 需要使用的文件】run.sh:【Linux 平台下运行 PMD 需要使用的文件】
lib【该目录存放 PMD 运行依赖的 jar 包,包括第三方 jar 包和各种语言的模块 jar 包】
AST
关于 AST 的介绍网上有很多,可以直接搜索,这里重要提两点:
- AST 是源代码的抽象语法结构的树状表示.
- 抽象语法树并不依赖于原语言的语法,也就是说同语法分析阶段所采用的上下文无关.
PMD 使用 JavaCC 来生成 AST。关于 JavaCC 也可以在网上查看相关资料,这里不多介绍,只要知道 JavaCC 是一个词法分析生成器和语法分析生成器便行。
开始实践
确定自定义规则
自定义规则: 方法参数不能超过5个,如果参数无法减少,可以将多个参数封装成一个对象
列举会触犯这种规则的所有不同的写法。
public void fn(int a, int b, int c,int d, int f, int g) {
dosomething();
}
使用 designer.bat 分析所有写法的抽象语法树的特点
如果在启动 designer.bat 时报错,则需要安装 JavaFX
安装 JavaFX
下载 JavaFX
https://gluonhq.com/products/javafx/

解压保存在自定义的目录下
我保存在了 JDK 目录下

配置环境变量
JAVAFX_HOME

在 Path 中配置 lib 和 bin 的路径

配置完成后在次执行 designer.bat 即可

开始分析
不可以直接分析方法,需要包在一个类中
public class Example {
public void fn(int a, int b, int c,int d, int f, int g) {
// ...
}
}

==注意==: 这个树形结构和源代码是有对应关系的。其中我们需要重点关注的 FormalParameters 的抽象树结构如下:注意标红的节点,根据定义的方法参数, FormalParameter 这个节点会对应的增加或减少,这样我们只需要写一个规则检查 FormalParameters 下的 FormalParameter 节点是否大于5就可以了,就可以报警告知这里是有问题的。
具体详细的节点的信息可以看对应的 jar 文件

编写规则代码捕捉这种特点
代码规范实现的主要模块,使用 pmd 来实现。p3c-pmd 模块在代码组织上很工整,可以按照相同的模式增加自定义的规则/规则集。对于本文需求,打算在该模块的基础上增加一个 extend 模块,用于实现自定义规则集。如下,为对应的源码路径好规则集路径。

public class MethodParamsNumRule extends AbstractAliRule {
private static final int PARAMSNUM = 5;
@Override
public Object visit(ASTFormalParameters node, Object data) {
if (node.jjtGetNumChildren()>PARAMSNUM) {
addViolationWithMessage(data,node,"java.extend.MethodParamsNumRule.rule.msg");
};
return super.visit(node, data);
}
}
创建自己的规则
创建自己的 xml 规则文件,内容包括规则的相关信息
现在规则已经写完了,我们需要告诉 PMD 运行时执行这条规则,就得将这个规则文件的相关信息放在 XML 规则集文件中。例如: pmd-java/src/main/resources/rulesets/java/extend.xml ;这里面有很多规则的定义,复制粘贴一下,改成一个新的规则集文件,名字自己随便取: MethodParamsNumRule.xml ,自己填充一下元素和属性。
Name- MethodParamsNumRuleMessage- java. Extend. MethodParamsNumRule. Rule. Msgclass- com. Alibaba. P 3 c. Pmd. Lang. Java. Rule. Extend. MethodParamsNumRul 放哪都行. 注意,没有必要放在net.sourceforge.pmd目录下,可以放在com.yourcompany.util.pmdDescription- 具体描述信息Example- 通过代码片段展示违反的规则样例

<?xml version="1.0"?>
<ruleset name="AlibabaJavaExceptions" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>ExtendJavaExceptions</description>
<!-- 方法的参数 -->
<rule name="MethodParamsNumRule"
language="java"
message="java.extend.MethodParamsNumRule.rule.msg"
class="com.xenoamess.p3c.pmd.lang.java.rule.extend.MethodParamsNumRule">
<priority>2</priority>
<example>
<![CDATA[
public class Example {
public void fn(int a, int b, int c,int d, int f, int g) {
dosomething();
}
}
]]>
</example>
</rule>
</ruleset>
java.extend.MethodParamsNumRule.rule.msg 设置在这里

messages.xml
<entry key="java.extend.MethodParamsNumRule.rule.msg">
<![CDATA[
方法的参数不要超过5个,如果确实需要5个以上的参数,请创建参数对象。
]]>
</entry>
messages_en.xml
<entry key="java.extend.MethodParamsNumRule.rule.msg">
<![CDATA[
The parameters of the method should not exceed 5. If you really need more than 5 parameters, please create a parameter object.
]]>
</entry>
测试规则
PMD 推荐对于每个规则,至少要有一个正向和逆向的测试用例,来验证规则出现和不出现的情况。对于规则的测试,PMD 也提供了一套框架,只要按照约定好的方式添加 xml 测试文件即可。
PMD 约定了几个规则,用来加载测试案例
- 测试类要继承 `net.sourceforge.pmd.testframework.SimpleAggregatorTst类,该整合了Junt,可以在里面增加需要的测试方法。
- 对于在
src/test/resource和测试类对应的路径下增加一个 xml 目录,在增加同第一步同名的 xml 文件,该文件用于书写测试集。
编写测试代码

public class MethodParamsNumRuleTest extends SimpleAggregatorTst {
private static final String RULESET = "java-ali-extend";
@Override
public void setUp() {
addRule(RULESET, "MethodParamsNumRule");
}
}
编写测试 XML

<?xml version="1.0" encoding="UTF-8"?>
<test-data xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests https://pmd.sourceforge.io/rule-tests_1_0_0.xsd">
<code-fragment id="constants-ok"><![CDATA[
public class Add{
public void fn(int a) {
// ...
}
public void fn(int a,int b) {
// ...
}
public void fn(int a,int b,int c) {
// ...
}
}
]]>
</code-fragment>
<test-code>
<description>params less than five</description>
<expected-problems>0</expected-problems>
<code-ref id="constants-ok"/>
</test-code>
<code-fragment id="constants-err"><![CDATA[
public class Add{
public void fn(int a,int b,int c,int d,int e,int f) {
// ...
}
}
]]>
</code-fragment>
<test-code>
<description>params more than five</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>2</expected-linenumbers>
<code-ref id="constants-err"/>
</test-code>
</test-data>
开始测试
- 执行 mvn clean
- 执行 mvn test
- 执行 mvn install 安装到本地,方便后面 idea-plugin 项目打包使用