AbstractBaseJavaLocalInspectionTool 是 IntelliJ IDEA 插件开发中用于实现 Java 代码本地检查的核心基类,其功能特性及实现逻辑如下:
一、核心功能
- 代码静态分析
继承该类的插件可对 Java 文件进行语法树解析,识别潜在问题(如代码规范、安全隐患等),并在编辑器中实时提示用户。 - 自定义检查规则
开发者需重写buildVisitor方法,通过访问 PSI(Program Structure Interface)元素实现特定检查逻辑。例如检测未使用的变量或不符合命名规范的代码段。 - 多级问题分类
支持通过ProblemHighlightType定义问题的严重程度(如错误、警告、弱警告),并通过registerProblem方法在代码位置标注提示信息。
代码实现
- 代码分析逻辑
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;
}
};
}
}
- 工具生效配置
<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>
- 添加方法注解
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());
});
}
}
}
- 添加成员变量注解
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());
}
});
}
}
- 添加类注解
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());
}
});
}
}
- 自动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);
}
}