IDEA 插件开发:IDEA 插件扫描项目源码

| 分类 好资源之开发工具  | 标签 MacOS  IDEA  Java  JavaEE  JDK  IDE  环境变量  IDEA插件开发  Action  p3c  PSI  PsiFile  AnalysisScope  VirtualFile 

上一篇文章的效果是当你打开某个源文件的时候,会在IDEA 中针对不符合规范的代码进行提示,但是有的时候我想要扫描整个项目的代码,输出一份代码质量报告,这个怎么实现?

先看了一些关于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

IntelliJ IDEA 插件开发指南

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 的源码好好学习一下人家的插件项目结构是怎么组织的!

到目前为止,关于插件开发、源码静态检视、项目源码扫描的基础知识已经具备,接下来最重要的就是将团队项目的规范实现到插件中!以及在这个过程中研究更多关于插件的使用技巧!

《Java AnalysisScope类代码示例》




如果本篇文章对您有所帮助,您可以通过微信(左)或支付宝(右)对作者进行打赏!


上一篇     下一篇