上一篇文章的效果是当你打开某个源文件的时候,会在IDEA 中针对不符合规范的代码进行提示,但是有的时候我想要扫描整个项目的代码,输出一份代码质量报告,这个怎么实现?
先看了一些关于IDEA 插件开发的文章
- 《IntelliJ IDEA 插件开发》第一节:两种方式创建插件工程
- 《IntelliJ IDEA 插件开发》第二节:开发摸鱼看书的侧边栏窗体
- 《IntelliJ IDEA 插件开发》第三节:开发工具栏和Tab页,展示股票行情和K线
- 《IntelliJ IDEA 插件开发》第四节:扩展创建工程向导步骤,开发DDD脚手架
- 《IntelliJ IDEA 插件开发》第五节:IDEA工程右键菜单,自动生成ORM代码
- 《IntelliJ IDEA 插件开发》第六节:选定对象批量织入“x.set(y.get)”代码,自动生成vo2dto
- 《IntelliJ IDEA 插件开发》第七节:通过Inspection机制,对静态代码安全审查
- 《IntelliJ IDEA 插件开发》第八节:在插件中引入探针,基于字节码插桩获取执行SQL
- 方案设计:基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析
虽然上面的相关文章无法满足我的需求,但也是很好的了解IDEA 插件开发的资料!
找到了阿里开发的p3c 插件,可以实现源码扫描,那么试着去研究一下这个插件:https://github.com/alibaba/p3c
Virtual Files
虚拟文件VirtualFile(VF)是在IntelliJ 的虚拟文件系统(VFS)中的文件表示。虚拟文件即本地文件系统中的文件
不过,虚拟文件也可以表示JAR文件中的文件:
Library library = LibraryUtil.findLibraryByClass(psiClass.getQualifiedName(), project);
VirtualFile YmlFile = library.getFiles(OrderRootType.CLASSES)[0].findChild(TModuleExplorer.T_YAML);
PsiFIle 和 VF 互转
PsiFile psiFile = PsiManager.getInstance(serverModule.getProject()).findFile(virtualFile);
VirtualFile virtualFile = psiFile.getVirtualFile();
PSI 与PsiFile
PSI(Program Structure Interface),程序结构接口,主要负责解析文件、创建语法、语义代码
IDEA 把整个工程的所有元素解析成了它们设计实现的PsiElement,你可以用它们提供的API 很方便的去CURD 所有元素。IDEA 也支持自定义语言,比较复杂了,需要实现Parser、PsiElement、FileType、Visitor等,有兴趣的话可以看看这个插件https://github.com/JetBrains/intellij-sdk-code-samples/tree/main/simple_language_plugin
com.intellij.psi.PsiFile 是文件结构的根,表示文件的内容为特定语言中元素的层次结构。它是所有PSI 文件的公共基类,而在特定的语言文件通常是由它的子类来表示。例如,PsiJavaFile 该类表示Java 文件,而,XmlFile 该类表示XML 文件
在IDE 所管理的Project 中,每个目录、Package、源代码和资源文件都会被抽象成相应的PSI 对象。常用子类:PsiDirectory、PsiJavaFile 和XmlFile
//创建目录
PsiDirectory baseDir =PsiDirectoryFactory.getInstance(project).createDirectory(project.getBaseDir());
//创建 Java 文件
PsiJavaFile psiFile = (PsiJavaFile) PsiFileFactory.getInstance(project).createFileFromText("",StdFileTypes.JAVA, "");
//创建 Xml 文件
XmlFile psiFile = (XmlFile) PsiFileFactory.getInstance(project).createFileFromText("",StdFileTypes.XML, "");
为了方便理解什么是PSI,打开IDEA 项目的某个Java 文件,然后【Tools】->【View PSI Structure of Current File…】

经过上面动态图的展示,什么是PSI 就更直观了!!!
- 一个Java 文件是一个PsiJavaFile
- package 关键字是PsiKeyword
- 空格是PsiWhiteSpace
- 包路径是PsiJavaCodeReferenceElement
- import 所有引入是PsiImportList
- 具体的一个import 是PsiImportStatement
- 注释是PsiDocCommet
- 一个Java 类是PsiClass
- 一个Java 方法是PsiMethod
- 其他
相当于把一个Java 文件做好语法分析了,我们直接可以获取其中的要素进行分析,比如类名是否符合驼峰命名规范、函数的参数是否过多、注释是否符合规范等等
基于PSI 开发代码扫描功能
基于上面对于PSI 的介绍,我们直接做一个源码扫描插件,在plugin.xml 中增加一个Action,然后在项目工程中【右键】->【Code Scan】触发调用
<action id="CodeScanAction" class="com.xum.action.CodeScanAction" text="Code Scan"
description="CodeScanAction">
<add-to-group group-id="ProjectViewPopupMenu" anchor="first"/>
</action>
然后编写程序如下
package com.xum.action;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.ex.InspectionManagerEx;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.compress.utils.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 在项目上右键->【源码规约扫描】
*/
public class CodeScanAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e)
{
Project project = e.getProject();
if (null == project) {
return;
}
InspectionManagerEx managerEx = (InspectionManagerEx) InspectionManager.getInstance(project);
// 获取项目对应的PsiElement
PsiElement projectElement = e.getData(CommonDataKeys.PSI_ELEMENT);
// 递归遍历
StringBuilder sb = new StringBuilder();
foreachChildren(projectElement, sb);
// 弹出框
Messages.showMessageDialog(project, sb.toString(),"All Class Name", Messages.getInformationIcon());
}
private void foreachChildren(PsiElement psiElement, StringBuilder sb)
{
for (PsiElement element: psiElement.getChildren())
{
if (element instanceof PsiJavaFile)
{
sb.append(checkPsiJavaFile((PsiJavaFile) element));
sb.append('\n');
} else {
foreachChildren(element, sb);
}
}
}
private String checkPsiJavaFile(PsiJavaFile javaFile)
{
// 打印类名
for (PsiElement element: javaFile.getChildren())
{
if (element instanceof PsiClass)
{
PsiClass psiClass = (PsiClass) element;
return psiClass.getName();
}
}
return "";
}
}
当前项目的结构如下

打包安装之后,在项目工程中【右键】->【Code Scan】触发调用,运行效果如下(因为out 中也有,虽然是.class 而不是.java,所以每个类显示了两次)

多说一句,在p3c 中还用到了AnalysisScope 等方案,这个后续自己看p3c 的源码好好学习一下人家的插件项目结构是怎么组织的!
到目前为止,关于插件开发、源码静态检视、项目源码扫描的基础知识已经具备,接下来最重要的就是将团队项目的规范实现到插件中!以及在这个过程中研究更多关于插件的使用技巧!