mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
[codeInsight] IDEA-201714 Missing "fix all" for "Redundant throws clause"
This patch fixes the problem with the absent the "fix all" quick fix for redundant throws clause inspection. It also does a major refactoring in the both global and local redundant throws clause exceptions applying modern code technics. Signed-off-by: Nikita Eshkeev <nikita.eshkeev@jetbrains.com> GitOrigin-RevId: 9336c9afd9a6c64b12d4e29f58e1c9da4f33e25e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
389ae1573b
commit
03c771b5bc
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.analysis.AnalysisScope;
|
||||
import com.intellij.codeInspection.reference.RefManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -2,41 +2,39 @@
|
||||
package com.intellij.codeInspection.unneededThrows;
|
||||
|
||||
import com.intellij.analysis.AnalysisScope;
|
||||
import com.intellij.codeInsight.ExceptionUtil;
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.MethodThrowsFix;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.reference.*;
|
||||
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
|
||||
import com.intellij.codeInspection.unneededThrows.RedundantThrowsDeclarationLocalInspection.ThrowRefType;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.Query;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance(RedundantThrowsDeclarationInspection.class);
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspectionTool {
|
||||
public boolean IGNORE_ENTRY_POINTS = false;
|
||||
|
||||
private final RedundantThrowsDeclarationLocalInspection myLocalInspection = new RedundantThrowsDeclarationLocalInspection(this);
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent createOptionsPanel() {
|
||||
return new SingleCheckboxOptionsPanel(JavaAnalysisBundle.message("ignore.exceptions.thrown.by.entry.points.methods"), this, "IGNORE_ENTRY_POINTS");
|
||||
@@ -48,80 +46,54 @@ public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspect
|
||||
@NotNull InspectionManager manager,
|
||||
@NotNull GlobalInspectionContext globalContext,
|
||||
@NotNull ProblemDescriptionsProcessor processor) {
|
||||
if (refEntity instanceof RefMethod) {
|
||||
final RefMethod refMethod = (RefMethod)refEntity;
|
||||
if (refMethod.isSyntheticJSP()) return null;
|
||||
if (!(refEntity instanceof RefMethod)) return null;
|
||||
|
||||
// if (refMethod.hasSuperMethods()) return null;
|
||||
final RefMethod refMethod = (RefMethod)refEntity;
|
||||
if (refMethod.isSyntheticJSP()) return null;
|
||||
|
||||
if (IGNORE_ENTRY_POINTS && refMethod.isEntry()) return null;
|
||||
if (IGNORE_ENTRY_POINTS && refMethod.isEntry()) return null;
|
||||
|
||||
PsiClass[] unThrown = refMethod.getUnThrownExceptions();
|
||||
if (unThrown == null) return null;
|
||||
final PsiClass[] unThrown = refMethod.getUnThrownExceptions();
|
||||
if (unThrown == null) return null;
|
||||
|
||||
PsiElement psiMethod = refMethod.getPsiElement();
|
||||
if (!(psiMethod instanceof PsiMethod)) return null;
|
||||
final PsiElement element = refMethod.getPsiElement();
|
||||
if (!(element instanceof PsiMethod)) return null;
|
||||
|
||||
if (((PsiMethod)psiMethod).hasModifier(JvmModifier.NATIVE)) return null;
|
||||
final PsiMethod method = (PsiMethod)element;
|
||||
|
||||
PsiReferenceList list = ((PsiMethod)psiMethod).getThrowsList();
|
||||
PsiClassType[] throwsList = list.getReferencedTypes();
|
||||
PsiJavaCodeReferenceElement[] throwsRefs = list.getReferenceElements();
|
||||
List<ProblemDescriptor> problems = null;
|
||||
if (method.hasModifier(JvmModifier.NATIVE)) return null;
|
||||
if (JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass())) return null;
|
||||
|
||||
final PsiManager psiManager = psiMethod.getManager();
|
||||
for (int i = 0; i < throwsList.length; i++) {
|
||||
final PsiClassType throwsType = throwsList[i];
|
||||
final String throwsClassName = throwsType.getClassName();
|
||||
final PsiJavaCodeReferenceElement throwsRef = throwsRefs[i];
|
||||
if (ExceptionUtil.isUncheckedException(throwsType)) continue;
|
||||
if (declaredInRemotableMethod((PsiMethod)psiMethod, throwsType)) continue;
|
||||
final Set<PsiClass> unThrownSet = ContainerUtil.set(unThrown);
|
||||
|
||||
for (PsiClass s : unThrown) {
|
||||
final PsiClass throwsResolvedType = throwsType.resolve();
|
||||
if (psiManager.areElementsEquivalent(s, throwsResolvedType)) {
|
||||
if (problems == null) problems = new ArrayList<>(1);
|
||||
|
||||
RefClass ownerClass = refMethod.getOwnerClass();
|
||||
if (refMethod.isAbstract() || ownerClass != null && ownerClass.isInterface()) {
|
||||
problems.add(manager.createProblemDescriptor(throwsRef, JavaAnalysisBundle.message(
|
||||
"inspection.redundant.throws.problem.descriptor", "<code>#ref</code>"), new MyQuickFix(processor, throwsClassName), ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
false));
|
||||
}
|
||||
else if (!refMethod.getDerivedMethods().isEmpty()) {
|
||||
problems.add(manager.createProblemDescriptor(throwsRef, JavaAnalysisBundle.message(
|
||||
"inspection.redundant.throws.problem.descriptor1", "<code>#ref</code>"), new MyQuickFix(processor, throwsClassName), ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
false));
|
||||
}
|
||||
else {
|
||||
problems.add(manager.createProblemDescriptor(throwsRef, JavaAnalysisBundle.message(
|
||||
"inspection.redundant.throws.problem.descriptor2", "<code>#ref</code>"), new MyQuickFix(processor, throwsClassName), ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (problems != null) {
|
||||
return problems.toArray(CommonProblemDescriptor.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return RedundantThrowsDeclarationLocalInspection.getProblems(method, IGNORE_ENTRY_POINTS)
|
||||
.filter(throwRefType -> unThrownSet.contains(throwRefType.getType().resolve()))
|
||||
.map(throwRefType -> {
|
||||
final PsiElement throwsRef = throwRefType.getReference();
|
||||
final String message = getMessage(refMethod);
|
||||
final MyQuickFix fix = new MyQuickFix(processor, throwRefType.getType().getClassName(), IGNORE_ENTRY_POINTS);
|
||||
return manager.createProblemDescriptor(throwsRef, message, fix, ProblemHighlightType.LIKE_UNUSED_SYMBOL, false);
|
||||
})
|
||||
.toArray(CommonProblemDescriptor.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
private static boolean declaredInRemotableMethod(final PsiMethod psiMethod, final PsiClassType throwsType) {
|
||||
if (!throwsType.equalsToText("java.rmi.RemoteException")) return false;
|
||||
PsiClass aClass = psiMethod.getContainingClass();
|
||||
if (aClass == null) return false;
|
||||
PsiClass remote =
|
||||
JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.rmi.Remote", GlobalSearchScope.allScope(aClass.getProject()));
|
||||
return remote != null && aClass.isInheritor(remote, true);
|
||||
@NotNull
|
||||
private static String getMessage(@NotNull final RefMethod refMethod) {
|
||||
final RefClass ownerClass = refMethod.getOwnerClass();
|
||||
if (refMethod.isAbstract() || ownerClass != null && ownerClass.isInterface()) {
|
||||
return JavaAnalysisBundle.message("inspection.redundant.throws.problem.descriptor", "<code>#ref</code>");
|
||||
}
|
||||
if (!refMethod.getDerivedMethods().isEmpty()) {
|
||||
return JavaAnalysisBundle.message("inspection.redundant.throws.problem.descriptor1", "<code>#ref</code>");
|
||||
}
|
||||
|
||||
return JavaAnalysisBundle.message("inspection.redundant.throws.problem.descriptor2", "<code>#ref</code>");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean queryExternalUsagesRequests(@NotNull final RefManager manager, @NotNull final GlobalJavaInspectionContext globalContext,
|
||||
protected boolean queryExternalUsagesRequests(@NotNull final RefManager manager,
|
||||
@NotNull final GlobalJavaInspectionContext globalContext,
|
||||
@NotNull final ProblemDescriptionsProcessor processor) {
|
||||
manager.iterate(new RefJavaVisitor() {
|
||||
@Override public void visitElement(@NotNull RefEntity refEntity) {
|
||||
@@ -142,9 +114,9 @@ public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspect
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public QuickFix getQuickFix(String hint) {
|
||||
return new MyQuickFix(null, hint);
|
||||
@NotNull
|
||||
public QuickFix<ProblemDescriptor> getQuickFix(String hint) {
|
||||
return new MyQuickFix(null, hint, IGNORE_ENTRY_POINTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,19 +125,21 @@ public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspect
|
||||
return fix instanceof MyQuickFix ? ((MyQuickFix)fix).myHint : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
@Override
|
||||
public RefGraphAnnotator getAnnotator(@NotNull RefManager refManager) {
|
||||
return new RedundantThrowsGraphAnnotator(refManager);
|
||||
}
|
||||
|
||||
private static class MyQuickFix implements LocalQuickFix {
|
||||
private static final class MyQuickFix implements LocalQuickFix {
|
||||
private final ProblemDescriptionsProcessor myProcessor;
|
||||
private final String myHint;
|
||||
private final boolean myIgnoreEntryPoints;
|
||||
|
||||
MyQuickFix(final ProblemDescriptionsProcessor processor, final String hint) {
|
||||
MyQuickFix(final ProblemDescriptionsProcessor processor, final String hint, boolean ignoreEntryPoints) {
|
||||
myProcessor = processor;
|
||||
myHint = hint;
|
||||
myIgnoreEntryPoints = ignoreEntryPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,97 +149,81 @@ public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspect
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
|
||||
final PsiMethod psiMethod;
|
||||
final CommonProblemDescriptor[] problems;
|
||||
final RefMethod refMethod;
|
||||
|
||||
if (myProcessor != null) {
|
||||
RefElement refElement = (RefElement)myProcessor.getElement(descriptor);
|
||||
if (refElement instanceof RefMethod && refElement.isValid()) {
|
||||
RefMethod refMethod = (RefMethod)refElement;
|
||||
final CommonProblemDescriptor[] problems = myProcessor.getDescriptions(refMethod);
|
||||
if (problems != null) {
|
||||
removeExcessiveThrows(refMethod, null, problems);
|
||||
}
|
||||
}
|
||||
final RefEntity refElement = myProcessor.getElement(descriptor);
|
||||
if (!(refElement instanceof RefMethod) || !refElement.isValid()) return;
|
||||
|
||||
refMethod = (RefMethod)refElement;
|
||||
psiMethod = ObjectUtils.tryCast(refMethod.getPsiElement(), PsiMethod.class);
|
||||
|
||||
problems = myProcessor.getDescriptions(refMethod);
|
||||
}
|
||||
else {
|
||||
final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethod.class);
|
||||
if (psiMethod != null) {
|
||||
removeExcessiveThrows(null, psiMethod, new CommonProblemDescriptor[]{descriptor});
|
||||
}
|
||||
psiMethod = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethod.class);
|
||||
if (psiMethod == null) return;
|
||||
|
||||
refMethod = null;
|
||||
problems = new CommonProblemDescriptor[]{descriptor};
|
||||
}
|
||||
|
||||
removeExcessiveThrows(refMethod, psiMethod, problems);
|
||||
}
|
||||
|
||||
private void removeExcessiveThrows(@Nullable RefMethod refMethod, @Nullable final PsiModifierListOwner element, final CommonProblemDescriptor[] problems) {
|
||||
try {
|
||||
@Nullable final PsiMethod psiMethod;
|
||||
if (element == null) {
|
||||
LOG.assertTrue(refMethod != null);
|
||||
psiMethod = ObjectUtils.tryCast(refMethod.getPsiElement(), PsiMethod.class);
|
||||
}
|
||||
else {
|
||||
psiMethod = (PsiMethod)element;
|
||||
}
|
||||
if (psiMethod == null) return; //invalid refMethod
|
||||
final Project project = psiMethod.getProject();
|
||||
final PsiManager psiManager = PsiManager.getInstance(project);
|
||||
final List<PsiElement> refsToDelete = new ArrayList<>();
|
||||
for (CommonProblemDescriptor problem : problems) {
|
||||
final PsiElement psiElement = ((ProblemDescriptor)problem).getPsiElement();
|
||||
if (psiElement instanceof PsiJavaCodeReferenceElement) {
|
||||
final PsiJavaCodeReferenceElement classRef = (PsiJavaCodeReferenceElement)psiElement;
|
||||
final PsiType psiType = JavaPsiFacade.getElementFactory(psiManager.getProject()).createType(classRef);
|
||||
removeException(refMethod, psiType, refsToDelete, psiMethod);
|
||||
} else {
|
||||
final PsiReferenceList throwsList = psiMethod.getThrowsList();
|
||||
final PsiClassType[] classTypes = throwsList.getReferencedTypes();
|
||||
for (PsiClassType classType : classTypes) {
|
||||
final String text = classType.getClassName();
|
||||
if (Comparing.strEqual(myHint, text)) {
|
||||
removeException(refMethod, classType, refsToDelete, psiMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void removeExcessiveThrows(@Nullable final RefMethod refMethod,
|
||||
@Nullable final PsiMethod psiMethod,
|
||||
final CommonProblemDescriptor @Nullable [] problems) {
|
||||
if (psiMethod == null) return;
|
||||
if (problems == null) return;
|
||||
|
||||
//check read-only status for derived methods
|
||||
if (!FileModificationService.getInstance().preparePsiElementsForWrite(refsToDelete)) return;
|
||||
final Project project = psiMethod.getProject();
|
||||
final PsiManager psiManager = PsiManager.getInstance(project);
|
||||
|
||||
WriteAction.run(() -> {
|
||||
for (final PsiElement aRefsToDelete : refsToDelete) {
|
||||
if (aRefsToDelete.isValid()) {
|
||||
aRefsToDelete.delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiManager.getProject());
|
||||
|
||||
final Set<PsiElement> refsToDelete = Arrays.stream(problems)
|
||||
.map(problem -> (ProblemDescriptor)problem)
|
||||
.map(ProblemDescriptor::getPsiElement)
|
||||
.filter(psiElement -> psiElement instanceof PsiJavaCodeReferenceElement)
|
||||
.map(reference -> (PsiJavaCodeReferenceElement)reference)
|
||||
.map(factory::createType)
|
||||
.flatMap(psiClassType -> removeException(refMethod, psiClassType, psiMethod))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
//check read-only status for derived methods
|
||||
if (!FileModificationService.getInstance().preparePsiElementsForWrite(refsToDelete)) return;
|
||||
|
||||
WriteAction.run(() -> {
|
||||
for (final PsiElement element : refsToDelete) {
|
||||
new CommentTracker().deleteAndRestoreComments(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void removeException(RefMethod refMethod,
|
||||
PsiType exceptionType,
|
||||
List<? super PsiElement> refsToDelete,
|
||||
PsiMethod psiMethod) {
|
||||
ContainerUtil.addAll(refsToDelete, MethodThrowsFix.Remove.extractRefsToRemove(psiMethod, exceptionType));
|
||||
private StreamEx<PsiElement> removeException(@Nullable final RefMethod refMethod,
|
||||
@NotNull final PsiType exceptionType,
|
||||
@NotNull final PsiMethod psiMethod) {
|
||||
final StreamEx<PsiElement> elements = RedundantThrowsDeclarationLocalInspection.getProblems(psiMethod, myIgnoreEntryPoints)
|
||||
.filter(throwRefType -> exceptionType.isAssignableFrom(throwRefType.getType()))
|
||||
.map(ThrowRefType::getReference);
|
||||
|
||||
final Stream<PsiElement> tail;
|
||||
if (refMethod != null) {
|
||||
assert myProcessor != null;
|
||||
tail = refMethod.getDerivedMethods().stream()
|
||||
.filter(refDerived -> refDerived.getPsiElement() instanceof PsiMethod)
|
||||
.flatMap(refDerived -> removeException(refDerived, exceptionType, (PsiMethod)refDerived.getPsiElement()));
|
||||
|
||||
for (RefMethod refDerived : refMethod.getDerivedMethods()) {
|
||||
PsiElement method = refDerived.getPsiElement();
|
||||
if (method instanceof PsiMethod) {
|
||||
removeException(refDerived, exceptionType, refsToDelete, (PsiMethod)method);
|
||||
}
|
||||
}
|
||||
ProblemDescriptionsProcessor.resolveAllProblemsInElement(myProcessor, refMethod);
|
||||
} else {
|
||||
final Query<PsiMethod> query = OverridingMethodsSearch.search(psiMethod);
|
||||
query.forEach(m -> {
|
||||
removeException(null, exceptionType, refsToDelete, m);
|
||||
return true;
|
||||
});
|
||||
tail = query.findAll().stream()
|
||||
.flatMap(method -> removeException(null, exceptionType, method));
|
||||
}
|
||||
return elements.append(tail);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,7 +232,7 @@ public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspect
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
@Override
|
||||
public LocalInspectionTool getSharedLocalInspectionTool() {
|
||||
return myLocalInspection;
|
||||
|
||||
@@ -3,29 +3,46 @@ package com.intellij.codeInspection.unneededThrows;
|
||||
|
||||
import com.intellij.codeInsight.ExceptionUtil;
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.MethodThrowsFix;
|
||||
import com.intellij.codeInsight.javadoc.JavaDocUtil;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.siyeh.ig.JavaOverridingMethodUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.psi.PsiModifier.ABSTRACT;
|
||||
|
||||
@SuppressWarnings("InspectionDescriptionNotFoundInspection") // delegates
|
||||
public class RedundantThrowsDeclarationLocalInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
private final RedundantThrowsDeclarationInspection myGlobalTool;
|
||||
public final class RedundantThrowsDeclarationLocalInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
@NotNull private final RedundantThrowsDeclarationInspection myGlobalTool;
|
||||
|
||||
@TestOnly
|
||||
public RedundantThrowsDeclarationLocalInspection() {this(new RedundantThrowsDeclarationInspection());}
|
||||
public RedundantThrowsDeclarationLocalInspection() {
|
||||
this(new RedundantThrowsDeclarationInspection());
|
||||
}
|
||||
|
||||
public RedundantThrowsDeclarationLocalInspection(@NotNull RedundantThrowsDeclarationInspection tool) {myGlobalTool = tool;}
|
||||
public RedundantThrowsDeclarationLocalInspection(@NotNull final RedundantThrowsDeclarationInspection tool) {
|
||||
myGlobalTool = tool;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@@ -40,84 +57,183 @@ public class RedundantThrowsDeclarationLocalInspection extends AbstractBaseJavaL
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemDescriptor @Nullable [] checkMethod(@NotNull PsiMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
|
||||
return checkExceptionsNeverThrown(method, manager);
|
||||
public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getDisplayName() {
|
||||
return JavaAnalysisBundle.message("inspection.redundant.throws.display.name");
|
||||
}
|
||||
|
||||
private ProblemDescriptor @Nullable [] checkExceptionsNeverThrown(PsiMethod method,
|
||||
InspectionManager inspectionManager) {
|
||||
if (method instanceof SyntheticElement) return null;
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null || JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) return null;
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
|
||||
return new RedundantThrowsVisitor(holder, myGlobalTool.IGNORE_ENTRY_POINTS);
|
||||
}
|
||||
|
||||
PsiCodeBlock body = method.getBody();
|
||||
if (body == null) return null;
|
||||
@Contract(pure = true)
|
||||
static StreamEx<ThrowRefType> getProblems(@Nullable final PsiMethod method, final boolean ignoreEntryPoints) {
|
||||
if (method == null) return StreamEx.empty();
|
||||
if (ignoreEntryPoints && UnusedDeclarationInspectionBase.isDeclaredAsEntryPoint(method)) return StreamEx.empty();
|
||||
if (method.hasModifier(JvmModifier.NATIVE)) return StreamEx.empty();
|
||||
if (JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass())) return StreamEx.empty();
|
||||
|
||||
if (myGlobalTool.IGNORE_ENTRY_POINTS && UnusedDeclarationInspectionBase.isDeclaredAsEntryPoint(method)) {
|
||||
return null;
|
||||
}
|
||||
final PsiReferenceList throwsList = method.getThrowsList();
|
||||
final StreamEx<ThrowRefType> redundantInThrowsList = StreamEx.zip(throwsList.getReferenceElements(),
|
||||
throwsList.getReferencedTypes(),
|
||||
ThrowRefType::new);
|
||||
|
||||
ReferenceAndType[] thrownExceptions = getThrownCheckedExceptions(method);
|
||||
if (thrownExceptions.length == 0) return null;
|
||||
|
||||
PsiModifierList modifierList = method.getModifierList();
|
||||
boolean needCheckOverridingMethods = !(modifierList.hasModifierProperty(PsiModifier.PRIVATE) ||
|
||||
modifierList.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
modifierList.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
method.isConstructor() ||
|
||||
containingClass instanceof PsiAnonymousClass ||
|
||||
containingClass.hasModifierProperty(PsiModifier.FINAL));
|
||||
Collection<PsiClassType> unhandled = RedundantThrowsGraphAnnotator.getUnhandledExceptions(body, method, containingClass);
|
||||
List<ReferenceAndType> candidates = Arrays.stream(thrownExceptions)
|
||||
.filter(refAndType -> unhandled.stream().noneMatch(unhandledException -> unhandledException.isAssignableFrom(refAndType.type) || refAndType.type.isAssignableFrom(unhandledException)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (candidates.isEmpty()) return null;
|
||||
if (needCheckOverridingMethods) {
|
||||
Predicate<PsiMethod> methodContainsThrownExceptions = m -> m.getThrowsList().getReferencedTypes().length != 0;
|
||||
Stream<PsiMethod> overridingMethods = JavaOverridingMethodUtil.getOverridingMethodsIfCheapEnough(method, null, methodContainsThrownExceptions);
|
||||
if (overridingMethods == null) return null;
|
||||
|
||||
Iterator<PsiMethod> overridingMethodIt = overridingMethods.iterator();
|
||||
while (overridingMethodIt.hasNext()) {
|
||||
PsiMethod m = overridingMethodIt.next();
|
||||
PsiClassType[] overridingMethodThrownExceptions = m.getThrowsList().getReferencedTypes();
|
||||
|
||||
candidates.removeIf(refAndType -> {
|
||||
PsiClassType type = refAndType.type;
|
||||
return Arrays.stream(overridingMethodThrownExceptions).anyMatch(type::isAssignableFrom);
|
||||
final PsiDocComment comment = method.getDocComment();
|
||||
final Stream<ThrowRefType> redundantInJavadoc;
|
||||
if (comment != null) {
|
||||
redundantInJavadoc = Arrays.stream(comment.getTags())
|
||||
.filter(tag -> "throws".equals(tag.getName()))
|
||||
.flatMap(tag -> {
|
||||
final PsiClass aClass = JavaDocUtil.resolveClassInTagValue(tag.getValueElement());
|
||||
if (aClass == null) return Stream.empty();
|
||||
return Stream.of(new ThrowRefType(tag, TypeUtils.getType(aClass)));
|
||||
});
|
||||
|
||||
if (candidates.isEmpty()) return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
redundantInJavadoc = Stream.empty();
|
||||
}
|
||||
|
||||
return candidates.stream().map(exceptionType -> {
|
||||
PsiJavaCodeReferenceElement reference = exceptionType.ref;
|
||||
String description = JavaErrorBundle.message("exception.is.never.thrown", JavaHighlightUtil.formatType(exceptionType.type));
|
||||
LocalQuickFix quickFix = new MethodThrowsFix.Remove(method, exceptionType.type, false);
|
||||
return inspectionManager.createProblemDescriptor(reference, description, quickFix, ProblemHighlightType.LIKE_UNUSED_SYMBOL, true);
|
||||
}).toArray(ProblemDescriptor[]::new);
|
||||
return redundantInThrowsList.append(redundantInJavadoc)
|
||||
.filter(ThrowRefType::isCheckedException)
|
||||
.filter(p -> !p.isRemoteExceptionInRemoteMethod(method));
|
||||
}
|
||||
|
||||
private static ReferenceAndType[] getThrownCheckedExceptions(PsiMethod method) {
|
||||
return Stream
|
||||
.of(method.getThrowsList().getReferenceElements())
|
||||
.map(ref -> {
|
||||
PsiElement resolved = ref.resolve();
|
||||
return resolved instanceof PsiClass && !ExceptionUtil.isUncheckedException((PsiClass)resolved) ? new ReferenceAndType(ref) : null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(ReferenceAndType[]::new);
|
||||
private static final class RedundantThrowsVisitor extends JavaElementVisitor {
|
||||
|
||||
private @NotNull final ProblemsHolder myHolder;
|
||||
private final boolean myIgnoreEntryPoints;
|
||||
private RedundantThrowsVisitor(@NotNull final ProblemsHolder holder, final boolean ignoreEntryPoints) {
|
||||
myHolder = holder;
|
||||
myIgnoreEntryPoints = ignoreEntryPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull final PsiMethod method) {
|
||||
getProblems(method, myIgnoreEntryPoints)
|
||||
.filter(throwRefType -> !throwRefType.isThrownIn(method))
|
||||
.filter(throwRefType -> !throwRefType.isInOverridenOf(method))
|
||||
.forEach(throwRefType -> {
|
||||
final PsiElement reference = throwRefType.myReference;
|
||||
final PsiClassType exceptionType = throwRefType.myType;
|
||||
|
||||
final String description = JavaErrorBundle.message("exception.is.never.thrown", JavaHighlightUtil.formatType(exceptionType));
|
||||
final RedundantThrowsQuickFix fix = new RedundantThrowsQuickFix(exceptionType.getCanonicalText(), method.getName());
|
||||
myHolder.registerProblem(reference, description, ProblemHighlightType.LIKE_UNUSED_SYMBOL, fix);
|
||||
});
|
||||
}
|
||||
|
||||
private static final class RedundantThrowsQuickFix implements LocalQuickFix {
|
||||
|
||||
@NotNull private final String myMethodName;
|
||||
@NotNull private final String myExceptionName;
|
||||
private RedundantThrowsQuickFix(@NotNull final String exceptionName, @NotNull final String methodName) {
|
||||
myExceptionName = exceptionName;
|
||||
myMethodName = methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionName @NotNull String getName() {
|
||||
final String exceptionName = StringUtil.getShortName(myExceptionName);
|
||||
return QuickFixBundle.message("fix.throws.list.remove.exception", exceptionName, myMethodName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionFamilyName @NotNull String getFamilyName() {
|
||||
return QuickFixBundle.message("fix.throws.list.family");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
|
||||
final PsiElement elem = descriptor.getPsiElement();
|
||||
|
||||
final CommentTracker ct = new CommentTracker();
|
||||
ct.deleteAndRestoreComments(elem);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ReferenceAndType {
|
||||
private final PsiJavaCodeReferenceElement ref;
|
||||
private final PsiClassType type;
|
||||
static final class ThrowRefType {
|
||||
@NotNull private final PsiElement myReference;
|
||||
@NotNull private final PsiClassType myType;
|
||||
|
||||
private ReferenceAndType(@NotNull PsiJavaCodeReferenceElement ref) {
|
||||
this.ref = ref;
|
||||
type = JavaPsiFacade.getElementFactory(ref.getProject()).createType(ref);
|
||||
ThrowRefType(@NotNull final PsiElement reference, @NotNull final PsiClassType type) {
|
||||
myReference = reference;
|
||||
myType = type;
|
||||
}
|
||||
|
||||
private boolean isCheckedException() {
|
||||
return !ExceptionUtil.isUncheckedException(myType);
|
||||
}
|
||||
|
||||
private boolean isRemoteExceptionInRemoteMethod(@NotNull final PsiMethod psiMethod) {
|
||||
if (!myType.equalsToText("java.rmi.RemoteException")) return false;
|
||||
|
||||
final PsiClass containingClass = psiMethod.getContainingClass();
|
||||
if (containingClass == null) return false;
|
||||
|
||||
final JavaPsiFacade instance = JavaPsiFacade.getInstance(containingClass.getProject());
|
||||
final PsiClass remote = instance.findClass("java.rmi.Remote", GlobalSearchScope.allScope(containingClass.getProject()));
|
||||
return remote != null && containingClass.isInheritor(remote, true);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private boolean isInOverridenOf(@NotNull final PsiMethod method) {
|
||||
if (!isMethodPossiblyOverriden(method)) return false;
|
||||
|
||||
final Predicate<PsiMethod> methodContainsThrownExceptions = m -> !ArrayUtil.isEmpty(m.getThrowsList().getReferencedTypes());
|
||||
|
||||
final Stream<PsiMethod> overridingMethods = JavaOverridingMethodUtil.getOverridingMethodsIfCheapEnough(method,
|
||||
null,
|
||||
methodContainsThrownExceptions);
|
||||
if (overridingMethods == null) return true;
|
||||
|
||||
return overridingMethods.anyMatch(m -> isThrownIn(m) || isInThrowsListOf(m));
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private boolean isThrownIn(@NotNull final PsiMethod method) {
|
||||
if (method.hasModifierProperty(ABSTRACT)) return true;
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) return true;
|
||||
|
||||
if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) return true;
|
||||
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body == null) return true;
|
||||
|
||||
final Set<PsiClassType> unhandled = RedundantThrowsGraphAnnotator.getUnhandledExceptions(body, method, containingClass);
|
||||
|
||||
return unhandled.stream().anyMatch(myType::isAssignableFrom);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private boolean isInThrowsListOf(@NotNull final PsiMethod method) {
|
||||
return Arrays.stream(method.getThrowsList().getReferencedTypes())
|
||||
.anyMatch(myType::isAssignableFrom);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private static boolean isMethodPossiblyOverriden(@NotNull final PsiMethod method) {
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) return false;
|
||||
|
||||
final PsiModifierList modifierList = method.getModifierList();
|
||||
|
||||
return !(modifierList.hasModifierProperty(PsiModifier.PRIVATE) ||
|
||||
modifierList.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
modifierList.hasModifierProperty(PsiModifier.FINAL) ||
|
||||
method.isConstructor() ||
|
||||
containingClass instanceof PsiAnonymousClass ||
|
||||
containingClass.hasModifierProperty(PsiModifier.FINAL));
|
||||
}
|
||||
|
||||
@NotNull PsiElement getReference() {
|
||||
return myReference;
|
||||
}
|
||||
|
||||
@NotNull PsiClassType getType() {
|
||||
return myType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Remove 'IOException' from 'foo' throws list" "true"
|
||||
// "Fix all 'Redundant 'throws' clause' problems in file" "true"
|
||||
import java.io.*;
|
||||
|
||||
class A {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Remove 'IOException' from 'foo' throws list" "true"
|
||||
// "Fix all 'Redundant 'throws' clause' problems in file" "true"
|
||||
import java.io.*;
|
||||
|
||||
class A {
|
||||
|
||||
Reference in New Issue
Block a user