PMD 開發(fā)自定義 XPath 規(guī)則解析 lambda 的 AST

[TOC]

概述

最近想自定義 PMD 規(guī)則來檢測(cè)java代碼,發(fā)現(xiàn) XPath 挺好用的,剛好也解析出來了一個(gè) lambda 表達(dá)式的校驗(yàn),所以分享一下

0x1 概念介紹

AST

根據(jù) PMD規(guī)則介紹得知

image.png

大概意思就是:

在運(yùn)行規(guī)則之前,PMD 會(huì)把源文件格式化為 AST (抽象語法樹) 。 這個(gè)技術(shù)很多地方用到了,如 Eclipse 、 IDEA、JavaParser) 。這棵樹可以體現(xiàn)出來代碼的語法結(jié)構(gòu), 并且比源碼的信息還要豐富和規(guī)范,所以 PMD 是直接拿著規(guī)則去匹配 AST

XPath

AST 在 PMD 中被解析出來之后,格式是 xml 的,想去搜索里面的信息,就要用到 XPath 的技術(shù)
其實(shí)就是一種搜索 xml 元素的技術(shù),類似的技術(shù)還有 JQuery 搜索 html

語法介紹在這里 XPath語法

大概看一下 // / [] 就行,其它的用到再看

0x2 開發(fā)

pmd 支持的有兩種

  • XPath
  • Java Visit

XPath 的配置規(guī)則的話,需要看的文檔少,上手快,我選的就是這個(gè)
不過文檔從哪里看起呢?先不著急,看看別人是怎么玩的

p3c-pmd

阿里p3c

阿里內(nèi)置了很多規(guī)則,有的就是用 XPath 實(shí)現(xiàn)的,我們挑一個(gè)看看

AvoidPatternCompileInMethodRule

代碼截圖


image.png

代碼的作用是: 不能在方法體內(nèi)部出現(xiàn) Pattern.compile("xx"),因?yàn)檎齽t表達(dá)式編譯很消耗性能,應(yīng)該在常量里做這個(gè)步驟

我們可以看到規(guī)則的實(shí)現(xiàn)代碼很簡(jiǎn)單,其中紅框部分就是檢測(cè)代碼的規(guī)則
我摳出來格式化一下

//MethodDeclaration//PrimaryExpression
  [PrimaryPrefix/Name[@Image='Pattern.compile'] 
  and 
  PrimarySuffix/Arguments/ArgumentList/Expression
    /PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']]

Pattern.compile("xx")

逐一分析一下

//MethodDeclaration 從根節(jié)點(diǎn)開始,選擇所有 MethodDeclaration

//PrimaryExpression 在所有的 MethodDeclaration 的下面,繼續(xù)跳著選 PrimaryExpression

[到末尾] 篩選 PrimaryExpression 節(jié)點(diǎn)必須滿足后續(xù)兩個(gè)條件

  1. PrimaryPrefix/Name[@Image='Pattern.compile']
    PrimaryExpression 有一個(gè)子元素 PrimaryPrefix, 且 PrimaryPrefix 的子元素 name 值為 Pattern.compile
  2. PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']
    PrimaryExpression 有一個(gè)子元素 PrimarySuffix,這個(gè)子元素有個(gè)子元素Arguments,這個(gè)子元素有個(gè)子元素 ArgumentList,一直套娃到 PrimaryPrefix, PrimaryPrefix的子元素 Literal 滿足條件 @StringLiteral='true'

簡(jiǎn)單翻譯過來就是,方法是 Pattern.compile, 這個(gè)方法有入?yún)⑶沂亲址?/p>

看一下語法樹


image.png
  • 橙色是快速選擇的 //
  • 紅色是條件1
  • 藍(lán)色是條件2

lambda

我們接下來研究一下 lambda 的 AST (注意:我是帶著目的來研究特定場(chǎng)景、特定寫法的 lambda 的,結(jié)論不一定適合所有場(chǎng)景)

我的場(chǎng)景是 避免工具類濫用

        B layer1 = Null.of(() -> a.getB());
        C layer2 = Null.of(() -> a.getB().getC());
        Integer layer3 = Null.of(() -> a.getB().getC().getNumber());

我們的工具類方法 Null.of 里面會(huì)對(duì) NullPointerException 進(jìn)行捕獲,在捕獲到的時(shí)候返回一個(gè)默認(rèn)值
這個(gè)工具類主要是為了在處理獲取嵌套字段的時(shí)候書寫方便, 比如 Null.of(() -> a.getB().getC().getNumber()), 如果自己手寫 if (null != xxx) 需要寫很多層判斷,代碼會(huì)很惡心。
工具類的本意是好的,不過后面大家濫用了,在一層的時(shí)候也使用工具類。因?yàn)閘ambda表達(dá)式內(nèi)部有可能會(huì)發(fā)生空指針異常,而發(fā)生異常時(shí),因?yàn)閖vm要停下來獲取線程棧快照并填充到異常棧里,所以工具類性能較低,所以希望大家在一層的時(shí)候,自己手寫。
好,背景介紹完了,現(xiàn)在要分析一下語法了,先看一下對(duì)比圖

image.png

第二行相比第一行,代碼多了.getc(), AST 多了兩個(gè) PrimarySuffix 元素
第三行相比第二行,代碼多了.getNumber(), AST 多了兩個(gè) PrimarySuffix 元素

所以 lambda 的 AST 大概規(guī)律如下:


lambda-AST

所以我們要找的代碼Null.of(() -> a.getB())規(guī)律就是: suffix元素有且僅有一個(gè)

(圖是用 latex畫的)latex公式

開發(fā)自定義規(guī)則

規(guī)則

根據(jù) 阿里的 p3c-pmd 照葫蘆畫瓢,可以得到 XPath 表達(dá)式

//MethodDeclaration//PrimaryExpression[
    PrimaryPrefix/Name[@Image='Null.of']
    and
    PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/LambdaExpression
       /Expression/PrimaryExpression[count(PrimarySuffix)=1]
]

核心就是 PrimaryExpression[count(PrimarySuffix)=1]

開發(fā)

Java 代碼的話,理解都不用理解,直接把 阿里的 AvoidPatternCompileInMethodRule 文件內(nèi)容復(fù)制一下,然后改個(gè)名字,改一下 XPath, 改一下提示信息就可以了

code

還需要配置 規(guī)則xml, 簡(jiǎn)單的放在 阿里的文件里面就行,很簡(jiǎn)單就不贅述了
打包之后,運(yùn)行

image.png

大功告成

參考

PMD規(guī)則
PMD rule-designer
XPath語法
阿里p3c

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容