mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
GitOrigin-RevId: 7de7fb69e89b16488e66e1912a2f38dd61a1f7e8
367 lines
14 KiB
Java
367 lines
14 KiB
Java
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.intellij.codeInspection;
|
|
|
|
import com.intellij.codeInsight.TestFrameworks;
|
|
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
|
|
import com.intellij.codeInspection.options.OptPane;
|
|
import com.intellij.ide.highlighter.JavaFileType;
|
|
import com.intellij.java.JavaBundle;
|
|
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
|
|
import com.intellij.modcommand.*;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.roots.ProjectFileIndex;
|
|
import com.intellij.openapi.roots.ProjectRootManager;
|
|
import com.intellij.openapi.util.TextRange;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.pom.java.JavaFeature;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.psi.search.PackageScope;
|
|
import com.intellij.psi.search.PsiSearchHelper;
|
|
import com.intellij.psi.search.SearchScope;
|
|
import com.intellij.psi.search.searches.ReferencesSearch;
|
|
import com.intellij.psi.util.PsiMethodUtil;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.psi.util.PsiUtil;
|
|
import com.intellij.util.ObjectUtils;
|
|
import com.siyeh.ig.psiutils.CommentTracker;
|
|
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
|
import com.siyeh.ipp.imports.ReplaceOnDemandImportIntention;
|
|
import org.jetbrains.annotations.Nls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
public final class ExplicitToImplicitClassMigrationInspection extends AbstractBaseJavaLocalInspectionTool {
|
|
|
|
public boolean convertToIo = true;
|
|
|
|
@Override
|
|
public @NotNull Set<@NotNull JavaFeature> requiredFeatures() {
|
|
return Set.of(JavaFeature.IMPLICIT_CLASSES);
|
|
}
|
|
|
|
@Override
|
|
public @NotNull OptPane getOptionsPane() {
|
|
return OptPane.pane(
|
|
OptPane.checkbox("convertToIo",
|
|
JavaBundle.message("inspection.explicit.to.implicit.convert.to.io"))
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
|
return new JavaElementVisitor() {
|
|
@Override
|
|
public void visitClass(@NotNull PsiClass aClass) {
|
|
if (aClass.isInterface() || aClass.isRecord() || aClass.isEnum() || PsiUtil.isLocalOrAnonymousClass(aClass)) {
|
|
return;
|
|
}
|
|
|
|
if (aClass.getAnnotations().length > 0) return;
|
|
|
|
PsiElement lBrace = aClass.getLBrace();
|
|
PsiElement rBrace = aClass.getRBrace();
|
|
if (lBrace == null || rBrace == null) {
|
|
return;
|
|
}
|
|
|
|
if (aClass.getContainingClass() != null) {
|
|
return;
|
|
}
|
|
PsiFile file = aClass.getContainingFile();
|
|
if (!(file instanceof PsiJavaFile javaFile)) return;
|
|
boolean underRoot = javaFile.getPackageStatement() == null;
|
|
|
|
if (javaFile.getClasses().length != 1) {
|
|
return;
|
|
}
|
|
|
|
PsiJavaModule javaModule = JavaPsiModuleUtil.findDescriptorByElement(aClass);
|
|
if (javaModule != null) {
|
|
return;
|
|
}
|
|
|
|
String fileName = file.getName();
|
|
if (!fileName.endsWith(JavaFileType.DOT_DEFAULT_EXTENSION)) {
|
|
return;
|
|
}
|
|
|
|
String className = aClass.getName();
|
|
if (className == null ||
|
|
!className.equals(fileName.substring(0, fileName.length() - JavaFileType.DOT_DEFAULT_EXTENSION.length()))) {
|
|
return;
|
|
}
|
|
|
|
if (aClass.hasTypeParameters()) {
|
|
return;
|
|
}
|
|
|
|
if (!PsiMethodUtil.hasMainMethod(aClass)) {
|
|
return;
|
|
}
|
|
|
|
PsiClassType[] extendsListTypes = aClass.getExtendsListTypes();
|
|
if ((extendsListTypes.length != 0 && !onlyObjectExtends(extendsListTypes)) ||
|
|
aClass.getImplementsListTypes().length != 0) {
|
|
return;
|
|
}
|
|
|
|
if (aClass.hasModifierProperty(PsiModifier.SEALED) || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
|
return;
|
|
}
|
|
|
|
PsiMethod[] constructors = aClass.getConstructors();
|
|
if (constructors.length > 0) {
|
|
if (constructors.length > 1) {
|
|
return;
|
|
}
|
|
|
|
PsiMethod constructor = constructors[0];
|
|
if (constructor.hasParameters() ||
|
|
constructor.hasModifierProperty(PsiModifier.PRIVATE) ||
|
|
(constructor.getBody() != null && constructor.getBody().getStatements().length > 0)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Project project = holder.getProject();
|
|
PsiIdentifier classIdentifier = aClass.getNameIdentifier();
|
|
if (classIdentifier == null) {
|
|
return;
|
|
}
|
|
|
|
if (TestFrameworks.getInstance().isTestClass(aClass) ||
|
|
UnusedSymbolUtil.isImplicitUsage(aClass.getProject(), aClass)) {
|
|
return;
|
|
}
|
|
|
|
SearchScope scope;
|
|
if (underRoot) {
|
|
PsiPackage aPackage = JavaPsiFacade.getInstance(project).findPackage("");
|
|
if (aPackage == null) {
|
|
return;
|
|
}
|
|
scope = new PackageScope(aPackage, false, false);
|
|
}
|
|
else {
|
|
scope = aClass.getUseScope();
|
|
}
|
|
|
|
if (isOnTheFly && scope instanceof GlobalSearchScope globalSearchScope) {
|
|
final PsiSearchHelper searchHelper = PsiSearchHelper.getInstance(project);
|
|
final PsiSearchHelper.SearchCostResult cost =
|
|
searchHelper.isCheapEnoughToSearch(className, globalSearchScope, null);
|
|
if (cost == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
|
|
holder.registerPossibleProblem(aClass);
|
|
return;
|
|
}
|
|
}
|
|
|
|
PsiReference first = ReferencesSearch.search(aClass, scope).findFirst();
|
|
if (first != null) {
|
|
return;
|
|
}
|
|
|
|
if (PsiTreeUtil.hasErrorElements(aClass)) {
|
|
return;
|
|
}
|
|
if (underRoot) {
|
|
holder.registerProblem(aClass, new TextRange(0, classIdentifier.getTextRangeInParent().getEndOffset()),
|
|
JavaBundle.message("inspection.explicit.to.implicit.class.migration.name"),
|
|
new ReplaceWithImplicitClassFix(convertToIo));
|
|
}
|
|
else {
|
|
holder.registerProblem(aClass,
|
|
JavaBundle.message("inspection.explicit.to.implicit.class.migration.name"),
|
|
ProblemHighlightType.INFORMATION,
|
|
new TextRange(0, classIdentifier.getTextRangeInParent().getEndOffset()),
|
|
new ReplaceWithImplicitClassFix(convertToIo));
|
|
}
|
|
}
|
|
|
|
private static boolean onlyObjectExtends(PsiClassType[] types) {
|
|
if (types.length != 1) return false;
|
|
PsiClassType type = types[0];
|
|
return type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT);
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
private static class ReplaceWithImplicitClassFix extends ModCommandQuickFix {
|
|
|
|
private final boolean myConvertToIo;
|
|
|
|
private ReplaceWithImplicitClassFix(boolean io) { myConvertToIo = io; }
|
|
|
|
@Override
|
|
public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getFamilyName() {
|
|
return JavaBundle.message("inspection.explicit.to.implicit.class.migration.fix.name");
|
|
}
|
|
|
|
@Override
|
|
public final @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
|
PsiElement element = descriptor.getStartElement();
|
|
PsiFile file = element.getContainingFile();
|
|
if (!(file instanceof PsiJavaFile javaFile)) return ModCommand.nop();
|
|
PsiPackageStatement statement = javaFile.getPackageStatement();
|
|
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
|
|
VirtualFile sourceRoot = fileIndex.getSourceRootForFile(javaFile.getVirtualFile());
|
|
if (statement == null || sourceRoot == null) {
|
|
return ModCommand
|
|
.psiUpdate(element, (e, updater) -> applyFix(project, e));
|
|
}
|
|
return ModCommand.chooseAction(
|
|
JavaBundle.message("inspection.explicit.to.implicit.move.to.root.title"),
|
|
getCommandActionWithMovingToRoot(project),
|
|
ModCommand.psiUpdateStep(element, JavaBundle.message("inspection.explicit.to.implicit.move.to.root.delete.package"),
|
|
(e, updater) -> applyFix(project, e))
|
|
);
|
|
}
|
|
|
|
private @NotNull ModCommandAction getCommandActionWithMovingToRoot(@NotNull Project project) {
|
|
return new ModCommandAction() {
|
|
@Override
|
|
public Presentation getPresentation(@NotNull ActionContext context) {
|
|
return Presentation.of(getFamilyName());
|
|
}
|
|
|
|
@Override
|
|
public @NotNull ModCommand perform(@NotNull ActionContext context) {
|
|
int offset = context.offset();
|
|
PsiElement element = context.file().findElementAt(offset);
|
|
if (element == null) return ModCommand.nop();
|
|
PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class, false);
|
|
if (psiClass == null) return ModCommand.nop();
|
|
PsiFile containingFile = psiClass.getContainingFile();
|
|
if (containingFile == null) return ModCommand.nop();
|
|
ModCommand modCommand = ModCommand
|
|
.psiUpdate(psiClass, (e, updater) -> applyFix(project, e));
|
|
VirtualFile from = containingFile.getVirtualFile();
|
|
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
|
|
VirtualFile sourceRoot = fileIndex.getSourceRootForFile(from);
|
|
if (sourceRoot != null) {
|
|
modCommand = modCommand
|
|
.andThen(
|
|
ModCommand.moveFile(from, sourceRoot)
|
|
);
|
|
}
|
|
return modCommand;
|
|
}
|
|
|
|
@Override
|
|
public @NotNull String getFamilyName() {
|
|
return JavaBundle.message("inspection.explicit.to.implicit.move.to.root.move");
|
|
}
|
|
};
|
|
}
|
|
|
|
private void applyFix(@NotNull Project project, @NotNull PsiElement element) {
|
|
PsiFile containingFile = element.getContainingFile();
|
|
if (!(containingFile instanceof PsiJavaFile javaFile)) {
|
|
return;
|
|
}
|
|
PsiPackageStatement packageStatement = javaFile.getPackageStatement();
|
|
if (packageStatement != null) {
|
|
new CommentTracker().deleteAndRestoreComments(packageStatement);
|
|
}
|
|
PsiImportList list = javaFile.getImportList();
|
|
if (list != null) {
|
|
List<SmartPsiElementPointer<PsiImportStatementBase>> pointers = new ArrayList<>();
|
|
PsiImportStatementBase[] statements = list.getAllImportStatements();
|
|
for (PsiImportStatementBase statement : statements) {
|
|
SmartPsiElementPointer<PsiImportStatementBase> pointer = SmartPointerManager.createPointer(statement);
|
|
pointers.add(pointer);
|
|
}
|
|
|
|
for (SmartPsiElementPointer<PsiImportStatementBase> pointer : pointers) {
|
|
PsiImportStatementBase importStatementBase = pointer.getElement();
|
|
if (importStatementBase == null) continue;
|
|
if (!importStatementBase.isOnDemand()) continue;
|
|
ReplaceOnDemandImportIntention.replaceOnDemand(importStatementBase);
|
|
}
|
|
}
|
|
PsiClass psiClass = ObjectUtils.tryCast(element, PsiClass.class);
|
|
if (psiClass == null) {
|
|
return;
|
|
}
|
|
PsiElement lBrace = psiClass.getLBrace();
|
|
PsiElement rBrace = psiClass.getRBrace();
|
|
if (lBrace == null || rBrace == null || lBrace.getNextSibling() == null || rBrace.getPrevSibling() == null) {
|
|
return;
|
|
}
|
|
|
|
CommentTracker tracker = new CommentTracker();
|
|
String body = tracker.rangeText(lBrace.getNextSibling(), rBrace.getPrevSibling());
|
|
PsiImplicitClass newClass = PsiElementFactory.getInstance(project).createImplicitClassFromText(body, psiClass);
|
|
if (!(newClass.getContainingFile() instanceof PsiJavaFile dummyFile) ||
|
|
dummyFile.getImportList() == null ||
|
|
javaFile.getImportList() == null) {
|
|
return;
|
|
}
|
|
//it is necessary to resolve accurately inside a new implicit class
|
|
//collisions will be resolved after it
|
|
dummyFile.getImportList().replace(javaFile.getImportList());
|
|
PsiElement replaced = tracker.replace(psiClass, newClass);
|
|
if (!(replaced instanceof PsiImplicitClass implicitClass)) {
|
|
return;
|
|
}
|
|
|
|
tracker.insertCommentsBefore(implicitClass);
|
|
|
|
cleanMainMethod(implicitClass);
|
|
|
|
PsiFile replacedContainingFile = replaced.getContainingFile();
|
|
if (replacedContainingFile == null) return;
|
|
|
|
if (myConvertToIo && PsiUtil.isAvailable(JavaFeature.JAVA_LANG_IO, replacedContainingFile)) {
|
|
convertToIOMethods(replacedContainingFile);
|
|
}
|
|
|
|
JavaCodeStyleManager.getInstance(project).optimizeImports(replacedContainingFile);
|
|
}
|
|
|
|
private static void convertToIOMethods(@NotNull PsiFile file) {
|
|
if (!(file instanceof PsiJavaFile javaFile)) {
|
|
return;
|
|
}
|
|
List<SmartPsiElementPointer<PsiMethodCallExpression>> systemOutPrints = new ArrayList<>();
|
|
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(file.getProject());
|
|
javaFile.accept(new JavaRecursiveElementWalkingVisitor() {
|
|
@Override
|
|
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
|
if (MigrateToJavaLangIoInspection.isSystemOutPrintln(expression)) {
|
|
systemOutPrints.add(smartPointerManager.createSmartPsiElementPointer(expression));
|
|
}
|
|
super.visitMethodCallExpression(expression);
|
|
}
|
|
});
|
|
for (SmartPsiElementPointer<PsiMethodCallExpression> print : systemOutPrints) {
|
|
PsiMethodCallExpression element = print.getElement();
|
|
if (element == null) continue;
|
|
MigrateToJavaLangIoInspection.replaceToIO(element);
|
|
}
|
|
}
|
|
|
|
private static void cleanMainMethod(@NotNull PsiImplicitClass implicitClass) {
|
|
PsiMethod mainMethod = PsiMethodUtil.findMainInClass(implicitClass);
|
|
if (mainMethod == null) return;
|
|
PsiModifierList modifierList = mainMethod.getModifierList();
|
|
modifierList.setModifierProperty(PsiModifier.PUBLIC, false);
|
|
modifierList.setModifierProperty(PsiModifier.PROTECTED, false);
|
|
modifierList.setModifierProperty(PsiModifier.STATIC, false);
|
|
|
|
PsiParameterList parameterList = mainMethod.getParameterList();
|
|
if (parameterList.getParametersCount() == 0) return;
|
|
PsiParameter parameter = parameterList.getParameters()[0];
|
|
if (VariableAccessUtils.variableIsUsed(parameter, mainMethod)) return;
|
|
CommentTracker ct = new CommentTracker();
|
|
ct.deleteAndRestoreComments(parameter);
|
|
}
|
|
}
|
|
}
|