IDEA插件 - 静态代码语法检查

409 阅读2分钟

AbstractBaseJavaLocalInspectionTool 是 IntelliJ IDEA 插件开发中用于实现 Java 代码本地检查的核心基类,其功能特性及实现逻辑如下:

一、核心功能

  1. 代码静态分析
    继承该类的插件可对 Java 文件进行语法树解析,识别潜在问题(如代码规范、安全隐患等),并在编辑器中实时提示用户‌。
  2. 自定义检查规则
    开发者需重写 buildVisitor 方法,通过访问 PSI(Program Structure Interface)元素实现特定检查逻辑。例如检测未使用的变量或不符合命名规范的代码段‌。
  3. 多级问题分类
    支持通过 ProblemHighlightType 定义问题的严重程度(如错误、警告、弱警告),并通过 registerProblem 方法在代码位置标注提示信息‌。

代码实现

  1. 代码分析逻辑
public class SpecificationInspectionTool extends AbstractBaseJavaLocalInspectionTool {

    @Override
    public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
        return new JavaElementVisitor() {
            @Override
            public void visitField(@NotNull PsiField field) {
                super.visitField(field);
                PsiClass containingClass = field.getContainingClass();
                if ( isChangeFile(field.getContainingFile()) ) {
                    PsiAnnotation[] annotations = field.getAnnotations();
                    Boolean haveDongBootAnnotation = Boolean.FALSE;
                    for (PsiAnnotation annotation : annotations) {
                            haveDongBootAnnotation = Boolean.TRUE;
                            break;
                    }
                    if (!haveDongBootAnnotation) {
                        holder.registerProblem(
                                field.getNameIdentifier(),
                                "字段上没有xxxxx",
                                ProblemHighlightType.WARNING
                        );
                    }
                }
            }

            private Boolean isChangeFile(PsiFile containingFile) {
                ChangeListManager changeListManager = ChangeListManager.getInstance(containingFile.getProject());
                Collection<VirtualFile> changedFiles = changeListManager.getAffectedFiles();
                boolean retBol = changedFiles.stream().anyMatch(file -> {
                    return Objects.equals(file.getCanonicalPath(), containingFile.getVirtualFile().getCanonicalPath());
                });
                return retBol;
            }
        };
    }
}
  1. 工具生效配置
<extensions defaultExtensionNs="com.intellij">
  <localInspection
          language="JAVA"
          displayName="文档规范检查"
          groupPath="Java"
          groupBundle="messages.InspectionsBundle"
          groupKey="group.names.probable.bugs"
          enabledByDefault="true"
          level="WARNING"
          implementationClass="xx.xx.SpecificationInspectionTool"/>
</extensions>
  1. 添加方法注解
static public class AddMethodAnnotationFix implements LocalQuickFix {
    private final String annotationName;

    public AddMethodAnnotationFix(String annotationName) {
        this.annotationName = annotationName;
    }

    @Override
    public @NotNull String getFamilyName() {
        return "添加方法注解";
    }

    @Override
    public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
        PsiElement element = descriptor.getPsiElement();
        if (element instanceof PsiIdentifierImpl) {
            PsiMethod targetMethod = null;
            if (element instanceof PsiMethod) {
                targetMethod = (PsiMethod) element;
            } else if (element instanceof PsiIdentifier) {
                targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
            }
            if (targetMethod == null || !targetMethod.isValid()) return;
            PsiMethod  finalTargetMethod = targetMethod;
            WriteCommandAction.runWriteCommandAction(project, () -> {
                PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
                PsiAnnotation annotation = factory.createAnnotationFromText(
                        "@" + annotationName,
                        finalTargetMethod.getModifierList()
                );
                finalTargetMethod.getModifierList().addBefore(annotation, finalTargetMethod.getModifierList().getFirstChild());
            });
        }
    }
}
  1. 添加成员变量注解
public class AddFieldAnnotationFix implements LocalQuickFix {
    private final String annotationName;

    public AddFieldAnnotationFix(String annotationName) {
        this.annotationName = annotationName;
    }
    @Override
    public @NotNull String getFamilyName() {
        return "添加字段注解";
    }
    @Override
    public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
        PsiElement element = descriptor.getPsiElement();
        PsiField targetField = null;
        if (element instanceof PsiIdentifierImpl) {
            PsiElement parent = element.getParent();
            if (parent instanceof PsiField) {
                targetField = (PsiField) parent;
            }
        } else if (element instanceof PsiField) {
            targetField = (PsiField) element;
        }
        if (targetField == null || !targetField.isValid()) return;
        PsiField finalTargetField = targetField;
        WriteCommandAction.runWriteCommandAction(project, () -> {
            PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
            PsiAnnotation annotation = factory.createAnnotationFromText("@" + annotationName, finalTargetField);
            PsiModifierList modifierList = finalTargetField.getModifierList();
            if (modifierList != null) {
                modifierList.addBefore(annotation, modifierList.getFirstChild());
            }
        });
    }
}
  1. 添加类注解
static class AddClassAnnotationFix implements LocalQuickFix {
    private final String annotationName;
    public AddClassAnnotationFix(String annotationName) {
        this.annotationName = annotationName;
    }
    @Override
    public @NotNull String getFamilyName() {
        return "添加@Api注解";
    }
    @Override
    public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
        PsiElement element = descriptor.getPsiElement();
        PsiClass targetClass = null;
        if (element instanceof PsiClass) {
            targetClass = (PsiClass) element;
        } else if (element instanceof PsiIdentifier) {
            // 当标识符是类名时,通过父元素获取类定义
            targetClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
        }
        if (targetClass == null || !targetClass.isValid()) return;
        final PsiClass targetClassFinal = targetClass;
        WriteCommandAction.runWriteCommandAction(project, () -> {
            PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
            String annotationText = "@" + annotationName;
            PsiAnnotation annotation = factory.createAnnotationFromText(annotationText, targetClassFinal);
            // 插入到类修饰符列表的开头
            PsiModifierList modifierList = targetClassFinal.getModifierList();
            if (modifierList != null) {
                modifierList.addBefore(annotation, modifierList.getFirstChild());
            }
        });
    }
}
  1. 自动import 类 数据
private static void addImport(Project project, PsiJavaFileImpl javaFile, String annotationName) {
    PsiImportList importList = javaFile.getImportList();
    boolean exist = Arrays.stream(importList.getAllImportStatements())
            .anyMatch(importState -> Objects.equals("annotationName", importState.getImportReference().getQualifiedName()));
    if (exist) {
        return;
    }
    // 创建Import语句
    PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
    PsiClass importClass = JavaPsiFacade.getInstance(project)
            .findClass(annotationName, GlobalSearchScope.allScope(project));
    if (Objects.nonNull(importClass)) {
        PsiImportStatement importStatement = factory.createImportStatement(importClass);
        // 添加到Import列表末尾
        importList.add(importStatement);
    }
}