定製阿里程式碼檢查,實現你自己的程式碼規範檢查
幾個月前,阿里開源了p3c,我也接到了老大交給我的技術改造。是這樣的,app是老專案了,半年前接入了ARouter,由於Activity繁多,就沒有去全域性支援ARouter,這不,技術改造來了,就是定義一個規則,全域性的在專案裡面搜一遍,所有Activity如果沒有@Route()註解,就把它揪出來。那麼來吧。
於是到同性戀交友網站(github)上面,把阿里程式碼檢查(這裡附上鍊接https://github.com/alibaba/p3c)下下來之後就傻眼了,用Kotlin寫的,我尼瑪。。。好吧騷年,擼起袖子加油幹。學點Kotlin,總還是好的。
我們現在開啟idea-plugin這個專案,程式碼主要是在p3c-common這個包裡面,我們先來看一下包結構吧
這裡畫圈兩個包比較重要,inspection是所有的檢查邏輯,idea-sandbox是外掛輸出的位置。
那是哪裡呼叫inspection的呢?
class AliLocalInspectionToolProvider : InspectionToolProvider {
...
companion object {
val ruleInfoMap: MutableMap<String, RuleInfo> = Maps.newHashMap<String, RuleInfo>()
private val LOGGER = Logger.getInstance(AliLocalInspectionToolProvider::class.java)
val ruleNames: MutableList<String> = Lists.newArrayList<String>()!!
private val CLASS_LIST = Lists.newArrayList<Class<*>>()
private val nativeInspectionToolClass = arrayListOf<Class<out LocalInspectionTool>>(
AliMissingOverrideAnnotationInspection::class.java,
TuoMissingAcitvityRouteInspection::class.java,
AliAccessStaticViaInstanceInspection::class.java,
AliDeprecationInspection::class.java,
MapOrSetKeyShouldOverrideHashCodeEqualsInspection::class.java,
AliArrayNamingShouldHaveBracketInspection::class.java,
AliControlFlowStatementWithoutBracesInspection::class.java,
AliEqualsAvoidNullInspection::class.java,
AliLongLiteralsEndingWithLowercaseLInspection::class.java,
AliWrapperTypeEqualityInspection::class.java
)
...
}
...
}
我們看到這個陣列常量nativeInspectionToolClass,這裡裝的就是你點選檢查時候檢查的所有規則的類。
private fun initNativeInspection() {
val pool = ClassPool.getDefault()
pool.insertClassPath(ClassClassPath(DelegateLocalInspectionTool::class.java))
nativeInspectionToolClass.forEach {
pool.insertClassPath (ClassClassPath(it))
val cc = pool.get(DelegateLocalInspectionTool::class.java.name)
cc.name = "Delegate${it.simpleName}"
val ctField = cc.getField("forJavassist")
cc.removeField(ctField)
val itClass = pool.get(it.name)
val toolClass = pool.get(LocalInspectionTool::class.java.name)
val newField = CtField(toolClass, "forJavassist", cc)
cc.addField(newField, CtField.Initializer.byNew(itClass))
CLASS_LIST.add(cc.toClass())
}
}
就是在initNativeInspection這個初始化本地檢查的方法裡面遍歷的。ok。再往上程式碼我們就不看啦,因為我們只要模仿著寫一個Inspection類,然後手動新增到那個List,然後編譯外掛就ok了。
接下來就是該寫寫檢查的規則類了,這裡我以我自己需求出發來分析,然後你們只要舉一反三就好了。
/*
* 這邊要繼承BaseInspection,AliBaseInspection
* BaseInspection是IDEA原始碼裡面的基礎類
* AliBaseInspection是阿里提供的基礎類
* 這兩個類是一定要實現的
* */
class TuoMissingAcitvityRouteInspection : BaseInspection, AliBaseInspection {
private val messageKey = "com.alibaba.p3c.idea.inspection.tuotuo.TuoMissingAcitvityRouteInspection"
constructor()
/**
* For Javassist
*/
constructor(any: Any?) : this()
//規程的標題
override fun ruleName(): String {
return "MissingAcitvityRouteRule"
}
//錯誤資訊
override fun buildErrorString(vararg infos: Any): String = P3cBundle.getMessage("$messageKey.errMsg")
//錯誤提示標題
override fun getDisplayName(): String = "Your activity should add route"
//錯誤提示內容體
override fun getStaticDescription(): String? = "Your activity should add route !!!"
//錯誤的等級
override fun getDefaultLevel(): HighlightDisplayLevel = HighlightDisplayLevels.CRITICAL
//規則的短標題
override fun getShortName(): String = "TuoMissingAcitvityRoute"
//是否預設開啟
override fun isEnabledByDefault(): Boolean = true
//回撥一個遍歷的類,這個類是由IDEA自己去控制的
override fun buildVisitor(): BaseInspectionVisitor {
return MissingActivityRouteVisitor()
}
/*
* 這裡是規則的具體寫法,比較靈活,自己的業務可以看一下別的規則的實現來推導自己的實現
* 我這裡只要是如果這個類名字裡麵包含了Activity,就去拿它的註解,
* 如果註解為空,或者說註解列表裡面不包含"com.alibaba.android.arouter.facade.annotation.Route"
* 即表示沒有新增過路由,就會通過registerClassError(aClass)輸出錯誤資訊
* */
class MissingActivityRouteVisitor : BaseInspectionVisitor() {
override fun visitClass(aClass: PsiClass?) {
super.visitClass(aClass)
if (aClass == null) return
if (TextUtils.isEmpty(aClass.name) || !aClass.name!!.contains("Activity")) {
return
}
if (hasRouteOverrideAnnotation(aClass)) {
return
}
registerClassError(aClass)
}
private fun hasRouteOverrideAnnotation(
element: PsiModifierListOwner): Boolean {
val modifierList = element.modifierList ?: return false
val annotation = modifierList.findAnnotation("com.alibaba.android.arouter.facade.annotation.Route")
return annotation != null
}
}
}
其實程式碼檢查還提供一個快速修復的fix功能,比如官方提供的實現裡面有這樣一個場景,找到所有需要加@override但是沒有加上@override的方法,然後檢查出來之後,會通過
@Override
public JComponent createOptionsPanel() {
final MultipleCheckboxOptionsPanel panel =
new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox(InspectionGadgetsBundle.message(
"ignore.equals.hashcode.and.tostring"), "ignoreObjectMethods");
panel.addCheckbox(InspectionGadgetsBundle.message(
"ignore.methods.in.anonymous.classes"),
"ignoreAnonymousClassMethods");
return panel;
}
提供一個操作介面,點選後會呼叫
@Override
protected InspectionGadgetsFix buildFix(Object... infos) {
return new MissingOverrideAnnotationFix();
}
private static class MissingOverrideAnnotationFix
extends InspectionGadgetsFix {
@Override
@NotNull
public String getName() {
return InspectionGadgetsBundle.message(
"missing.override.annotation.add.quickfix");
}
@Override
@NotNull
public String getFamilyName() {
return getName();
}
@Override
public void doFix(Project project, ProblemDescriptor descriptor)
throws IncorrectOperationException {
final PsiElement identifier = descriptor.getPsiElement();
final PsiElement parent = identifier.getParent();
if (!(parent instanceof PsiModifierListOwner)) {
return;
}
final PsiModifierListOwner modifierListOwner =
(PsiModifierListOwner)parent;
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiElementFactory factory = psiFacade.getElementFactory();
final PsiAnnotation annotation =
factory.createAnnotationFromText("@java.lang.Override",
modifierListOwner);
final PsiModifierList modifierList =
modifierListOwner.getModifierList();
if (modifierList == null) {
return;
}
modifierList.addAfter(annotation, null);
}
}
dofix就是快速修復程式碼,這兩個都是通過BaseInspection實現的。由於我這邊的需求不需要智慧修復,就沒去寫fix啦。
然後寫完了程式碼就是打包外掛了,我這裡花了很多時間,給張截圖給老鐵們,蟹蟹捧場關注了
buildPlugin就可以啦,輸出檔案就在剛才講的p3c-idea/idea-sandbox下面啦。然後你就自己玩去吧。自己定義自己的程式碼檢查。哎呦,還蠻酷的哦!