几个月前,阿里开源了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 = 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啦。
然后写完了代码就是打包插件了,我这里花了很多时间,给张截图给老铁们,蟹蟹捧场关注了
buildPlugin就可以啦,输出文件就在刚才讲的p3c-idea/idea-sandbox下面啦。然后你就自己玩去吧。自己定义自己的代码检查。哎呦,还蛮酷的哦!
给出本人定制的代码规范的github地址(https://github.com/mengxin1995/p3c),可以看看提交记录学习。
参考文章 :AndroidStudio 插件开发(Hello World 篇)