[TOC]
概述
最近想自定义 PMD 规则来检测java代码,发现 XPath 挺好用的,刚好也解析出来了一个 lambda 表达式的校验,所以分享一下
0x1 概念介绍
AST
根据 PMD规则介绍得知

大概意思就是:
在运行规则之前,PMD 会把源文件格式化为 AST (抽象语法树) 。 这个技术很多地方用到了,如 Eclipse 、 IDEA、JavaParser) 。这棵树可以体现出来代码的语法结构, 并且比源码的信息还要丰富和规范,所以 PMD 是直接拿着规则去匹配 AST
XPath
AST 在 PMD 中被解析出来之后,格式是 xml 的,想去搜索里面的信息,就要用到 XPath 的技术
其实就是一种搜索 xml 元素的技术,类似的技术还有 JQuery 搜索 html
语法介绍在这里 XPath语法
大概看一下 // / [] 就行,其它的用到再看
0x2 开发
pmd 支持的有两种
- XPath
- Java Visit
用 XPath 的配置规则的话,需要看的文档少,上手快,我选的就是这个
不过文档从哪里看起呢?先不着急,看看别人是怎么玩的
p3c-pmd
阿里内置了很多规则,有的就是用 XPath 实现的,我们挑一个看看
AvoidPatternCompileInMethodRule
代码截图

代码的作用是: 不能在方法体内部出现 Pattern.compile("xx"),因为正则表达式编译很消耗性能,应该在常量里做这个步骤
我们可以看到规则的实现代码很简单,其中红框部分就是检测代码的规则
我抠出来格式化一下
//MethodDeclaration//PrimaryExpression
[PrimaryPrefix/Name[@Image='Pattern.compile']
and
PrimarySuffix/Arguments/ArgumentList/Expression
/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']]
Pattern.compile("xx")
逐一分析一下
//MethodDeclaration从根节点开始,选择所有 MethodDeclaration
//PrimaryExpression在所有的 MethodDeclaration 的下面,继续跳着选 PrimaryExpression
[到末尾]筛选 PrimaryExpression 节点必须满足后续两个条件
PrimaryPrefix/Name[@Image='Pattern.compile']
PrimaryExpression 有一个子元素 PrimaryPrefix, 且 PrimaryPrefix 的子元素 name 值为 Pattern.compilePrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']
PrimaryExpression 有一个子元素 PrimarySuffix,这个子元素有个子元素Arguments,这个子元素有个子元素 ArgumentList,一直套娃到 PrimaryPrefix, PrimaryPrefix的子元素Literal满足条件 @StringLiteral='true'
简单翻译过来就是,方法是 Pattern.compile, 这个方法有入参且是字符串
看一下语法树

- 橙色是快速选择的
// - 红色是条件1
- 蓝色是条件2
lambda
我们接下来研究一下 lambda 的 AST (注意:我是带着目的来研究特定场景、特定写法的 lambda 的,结论不一定适合所有场景)
我的场景是 避免工具类滥用
B layer1 = Null.of(() -> a.getB());
C layer2 = Null.of(() -> a.getB().getC());
Integer layer3 = Null.of(() -> a.getB().getC().getNumber());
我们的工具类方法 Null.of 里面会对 NullPointerException 进行捕获,在捕获到的时候返回一个默认值
这个工具类主要是为了在处理获取嵌套字段的时候书写方便, 比如 Null.of(() -> a.getB().getC().getNumber()), 如果自己手写 if (null != xxx) 需要写很多层判断,代码会很恶心。
工具类的本意是好的,不过后面大家滥用了,在一层的时候也使用工具类。因为lambda表达式内部有可能会发生空指针异常,而发生异常时,因为jvm要停下来获取线程栈快照并填充到异常栈里,所以工具类性能较低,所以希望大家在一层的时候,自己手写。
好,背景介绍完了,现在要分析一下语法了,先看一下对比图

第二行相比第一行,代码多了.getc(), AST 多了两个 PrimarySuffix 元素
第三行相比第二行,代码多了.getNumber(), AST 多了两个 PrimarySuffix 元素
所以 lambda 的 AST 大概规律如下:

所以我们要找的代码Null.of(() -> a.getB())规律就是: suffix元素有且仅有一个
(图是用 latex画的)latex公式
开发自定义规则
规则
根据 阿里的 p3c-pmd 照葫芦画瓢,可以得到 XPath 表达式
//MethodDeclaration//PrimaryExpression[
PrimaryPrefix/Name[@Image='Null.of']
and
PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/LambdaExpression
/Expression/PrimaryExpression[count(PrimarySuffix)=1]
]
核心就是 PrimaryExpression[count(PrimarySuffix)=1]
开发
Java 代码的话,理解都不用理解,直接把 阿里的 AvoidPatternCompileInMethodRule 文件内容复制一下,然后改个名字,改一下 XPath, 改一下提示信息就可以了

还需要配置 规则xml, 简单的放在 阿里的文件里面就行,很简单就不赘述了
打包之后,运行

大功告成