1. 程式人生 > >定製阿里程式碼檢查,實現你自己的程式碼規範檢查

定製阿里程式碼檢查,實現你自己的程式碼規範檢查

幾個月前,阿里開源了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下面啦。然後你就自己玩去吧。自己定義自己的程式碼檢查。哎呦,還蠻酷的哦!