定制阿里代码检查,实现你自己的代码规范检查

几个月前,阿里开源了p3c,我也接到了老大交给我的技术改造。是这样的,app是老项目了,半年前接入了ARouter,由于Activity繁多,就没有去全局支持ARouter,这不,技术改造来了,就是定义一个规则,全局的在项目里面搜一遍,所有Activity如果没有@Route()注解,就把它揪出来。那么来吧。

于是到同性恋交友网站(github)上面,把阿里代码检查(这里附上链接https://github.com/alibaba/p3c)下下来之后就傻眼了,用Kotlin写的,我尼玛。。。好吧骚年,撸起袖子加油干。学点Kotlin,总还是好的。

我们现在打开idea-plugin这个项目,代码主要是在p3c-common这个包里面,我们先来看一下包结构吧
定制阿里代码检查,实现你自己的代码规范检查_第1张图片
这里画圈两个包比较重要,inspection是所有的检查逻辑,idea-sandbox是插件输出的位置。
那是哪里调用inspection的呢?

class AliLocalInspectionToolProvider : InspectionToolProvider {
    ...
    companion object {
            val ruleInfoMap: MutableMap = Maps.newHashMap()
            private val LOGGER = Logger.getInstance(AliLocalInspectionToolProvider::class.java)
            val ruleNames: MutableList = Lists.newArrayList()!!
            private val CLASS_LIST = Lists.newArrayList>()
            private val nativeInspectionToolClass = arrayListOf>(
                    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啦。

然后写完了代码就是打包插件了,我这里花了很多时间,给张截图给老铁们,蟹蟹捧场关注了
定制阿里代码检查,实现你自己的代码规范检查_第2张图片
buildPlugin就可以啦,输出文件就在刚才讲的p3c-idea/idea-sandbox下面啦。然后你就自己玩去吧。自己定义自己的代码检查。哎呦,还蛮酷的哦!

给出本人定制的代码规范的github地址(https://github.com/mengxin1995/p3c),可以看看提交记录学习。

参考文章 :AndroidStudio 插件开发(Hello World 篇)

你可能感兴趣的:(android)