mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[java-inspections] Move unused symbol warnings from PostHighlightingVisitor to normal inspection
Fixes IDEA-349083 Reimplement unused inspection not as a part of PostHighlightingVisitor, but as a normal inspection GitOrigin-RevId: cb6e22eddbaf9db42626a79c7881bd377c4c7863
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2a97b0103c
commit
5ceb7551aa
@@ -60,7 +60,7 @@ public final class LocalRefUseInfo {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
GlobalUsageHelper getGlobalUsageHelper(@NotNull PsiFile file, @Nullable UnusedDeclarationInspectionBase deadCodeInspection) {
|
||||
public GlobalUsageHelper getGlobalUsageHelper(@NotNull PsiFile file, @Nullable UnusedDeclarationInspectionBase deadCodeInspection) {
|
||||
FileViewProvider viewProvider = file.getViewProvider();
|
||||
Project project = file.getProject();
|
||||
|
||||
@@ -134,7 +134,7 @@ public final class LocalRefUseInfo {
|
||||
* @param element element to check (variable, method, parameter, field, etc.)
|
||||
* @return true if the element is referenced in the same file
|
||||
*/
|
||||
boolean isReferenced(@NotNull PsiElement element) {
|
||||
public boolean isReferenced(@NotNull PsiElement element) {
|
||||
Collection<PsiReference> array = myLocalRefsMap.get(element);
|
||||
if (!array.isEmpty() &&
|
||||
!isParameterUsedRecursively(element, array) &&
|
||||
@@ -197,7 +197,7 @@ public final class LocalRefUseInfo {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isReferencedForRead(@NotNull PsiVariable variable) {
|
||||
public boolean isReferencedForRead(@NotNull PsiVariable variable) {
|
||||
Collection<PsiReference> array = myLocalRefsMap.get(variable);
|
||||
if (array.isEmpty()) return false;
|
||||
for (PsiReference ref : array) {
|
||||
@@ -231,7 +231,7 @@ public final class LocalRefUseInfo {
|
||||
refElement.getParent().getParent() instanceof PsiExpressionStatement;
|
||||
}
|
||||
|
||||
boolean isReferencedForWrite(@NotNull PsiVariable variable) {
|
||||
public boolean isReferencedForWrite(@NotNull PsiVariable variable) {
|
||||
Collection<PsiReference> array = myLocalRefsMap.get(variable);
|
||||
if (array.isEmpty()) return false;
|
||||
for (PsiReference ref : array) {
|
||||
|
||||
@@ -3,23 +3,16 @@ package com.intellij.codeInsight.daemon.impl.analysis;
|
||||
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.UnusedImportProvider;
|
||||
import com.intellij.codeInsight.daemon.impl.*;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceWithUnnamedPatternFix;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
|
||||
import com.intellij.codeInspection.ExternalSourceProblemGroup;
|
||||
import com.intellij.codeInspection.InspectionProfile;
|
||||
import com.intellij.codeInspection.SuppressionUtil;
|
||||
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileImpl;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileWrapper;
|
||||
import com.intellij.codeInspection.unusedImport.MissortedImportsInspection;
|
||||
import com.intellij.codeInspection.unusedImport.UnusedImportInspection;
|
||||
import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection;
|
||||
import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase;
|
||||
import com.intellij.java.analysis.JavaAnalysisBundle;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.lang.annotation.ProblemGroup;
|
||||
@@ -29,29 +22,15 @@ import com.intellij.openapi.editor.colors.TextAttributesKey;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.Predicates;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.impl.PsiClassImplUtil;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.search.searches.SuperMethodsSearch;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.VisibilityUtil;
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -60,12 +39,8 @@ class PostHighlightingVisitor extends JavaElementVisitor {
|
||||
@NotNull private final Project myProject;
|
||||
private final PsiFile myFile;
|
||||
@NotNull private final Document myDocument;
|
||||
private final GlobalUsageHelper myGlobalUsageHelper;
|
||||
private IntentionAction myOptimizeImportsFix; // when not null, there are not-optimized imports in the file
|
||||
private int myCurrentEntryIndex = -1;
|
||||
private final UnusedSymbolLocalInspection myUnusedSymbolInspection;
|
||||
private final HighlightDisplayKey myDeadCodeKey;
|
||||
private final HighlightInfoType myDeadCodeInfoType;
|
||||
private boolean errorFound;
|
||||
|
||||
PostHighlightingVisitor(@NotNull PsiFile file, @NotNull Document document) throws ProcessCanceledException {
|
||||
@@ -75,45 +50,10 @@ class PostHighlightingVisitor extends JavaElementVisitor {
|
||||
myFile = file;
|
||||
myDocument = document;
|
||||
myRefCountHolder = LocalRefUseInfo.forFile(file);
|
||||
InspectionProfileImpl profile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
|
||||
myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspectionBase.SHORT_NAME);
|
||||
UnusedDeclarationInspectionBase deadCodeInspection = (UnusedDeclarationInspectionBase)profile.getUnwrappedTool(UnusedDeclarationInspectionBase.SHORT_NAME, myFile);
|
||||
myUnusedSymbolInspection = deadCodeInspection == null ? null : deadCodeInspection.getSharedLocalInspectionTool();
|
||||
myDeadCodeInfoType = myDeadCodeKey == null ? HighlightInfoType.UNUSED_SYMBOL
|
||||
: new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(),
|
||||
ObjectUtils.notNull(profile.getEditorAttributes(myDeadCodeKey.toString(), myFile),
|
||||
HighlightInfoType.UNUSED_SYMBOL.getAttributesKey()));
|
||||
myGlobalUsageHelper = myRefCountHolder.getGlobalUsageHelper(myFile, deadCodeInspection);
|
||||
}
|
||||
|
||||
void collectHighlights(@NotNull HighlightInfoHolder holder) {
|
||||
ApplicationManager.getApplication().assertIsNonDispatchThread();
|
||||
if (myDeadCodeKey != null && isToolEnabled(myDeadCodeKey)) {
|
||||
TextRange priorityRange = holder.getAnnotationSession().getPriorityRange();
|
||||
JavaElementVisitor identifierVisitor = new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
|
||||
processIdentifier(holder, identifier);
|
||||
}
|
||||
};
|
||||
Divider.divideInsideAndOutsideAllRoots(myFile, myFile.getTextRange(), priorityRange, Predicates.alwaysTrue(), dividedElements -> {
|
||||
ProgressManager.checkCanceled();
|
||||
PsiFile psiRoot = dividedElements.psiRoot();
|
||||
HighlightingLevelManager highlightingLevelManager = HighlightingLevelManager.getInstance(myProject);
|
||||
if (!highlightingLevelManager.shouldInspect(psiRoot) || highlightingLevelManager.runEssentialHighlightingOnly(psiRoot)) {
|
||||
return true;
|
||||
}
|
||||
for (PsiElement element : dividedElements.inside()) {
|
||||
ProgressManager.checkCanceled();
|
||||
element.accept(identifierVisitor);
|
||||
}
|
||||
for (PsiElement element : dividedElements.outside()) {
|
||||
ProgressManager.checkCanceled();
|
||||
element.accept(identifierVisitor);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportInspection.SHORT_NAME);
|
||||
PsiJavaFile javaFile = ObjectUtils.tryCast(myFile, PsiJavaFile.class);
|
||||
@@ -161,7 +101,7 @@ class PostHighlightingVisitor extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
private boolean isToolEnabled(@NotNull HighlightDisplayKey displayKey) {
|
||||
if (!(myFile instanceof PsiJavaFile) || myUnusedSymbolInspection == null) {
|
||||
if (!(myFile instanceof PsiJavaFile)) {
|
||||
return false;
|
||||
}
|
||||
InspectionProfile profile = getCurrentProfile(myFile);
|
||||
@@ -177,360 +117,6 @@ class PostHighlightingVisitor extends JavaElementVisitor {
|
||||
return custom != null ? custom.apply(currentProfile).getInspectionProfile() : currentProfile;
|
||||
}
|
||||
|
||||
@NlsContexts.DetailedDescription private String message;
|
||||
private final List<IntentionAction> quickFixes = new ArrayList<>();
|
||||
private final List<IntentionAction> quickFixOptions = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
|
||||
if (myUnusedSymbolInspection.LOCAL_VARIABLE) {
|
||||
processLocalVariable(variable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
if (compareVisibilities(field, myUnusedSymbolInspection.getFieldVisibility())) {
|
||||
processField(myProject, field);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitParameter(@NotNull PsiParameter parameter) {
|
||||
PsiElement declarationScope = parameter.getDeclarationScope();
|
||||
boolean needToProcessParameter;
|
||||
if (declarationScope instanceof PsiMethod || declarationScope instanceof PsiLambdaExpression) {
|
||||
if (declarationScope instanceof PsiLambdaExpression) {
|
||||
declarationScope = PsiTreeUtil.getParentOfType(declarationScope, PsiModifierListOwner.class);
|
||||
}
|
||||
needToProcessParameter = compareVisibilities((PsiModifierListOwner)declarationScope, myUnusedSymbolInspection.getParameterVisibility());
|
||||
}
|
||||
else {
|
||||
needToProcessParameter = myUnusedSymbolInspection.LOCAL_VARIABLE;
|
||||
}
|
||||
if (needToProcessParameter) {
|
||||
processParameter(myProject, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
if (myUnusedSymbolInspection.isIgnoreAccessors() && PropertyUtilBase.isSimplePropertyAccessor(method)) {
|
||||
return;
|
||||
}
|
||||
if (compareVisibilities(method, myUnusedSymbolInspection.getMethodVisibility())) {
|
||||
processMethod(myProject, method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
String acceptedVisibility = aClass.getContainingClass() == null ? myUnusedSymbolInspection.getClassVisibility()
|
||||
: myUnusedSymbolInspection.getInnerClassVisibility();
|
||||
if (compareVisibilities(aClass, acceptedVisibility)) {
|
||||
processClass(myProject, aClass);
|
||||
}
|
||||
}
|
||||
|
||||
private void processIdentifier(@NotNull HighlightInfoHolder holder, @NotNull PsiIdentifier identifier) {
|
||||
PsiElement parent = identifier.getParent();
|
||||
if (parent == null) return;
|
||||
if ((parent instanceof PsiVariable || parent instanceof PsiMember) && SuppressionUtil.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return;
|
||||
if (parent instanceof PsiParameter && SuppressionUtil.isSuppressed(identifier, UnusedSymbolLocalInspection.UNUSED_PARAMETERS_SHORT_NAME)) return;
|
||||
|
||||
parent.accept(this);
|
||||
if (message != null) {
|
||||
HighlightInfo.Builder builder =
|
||||
UnusedSymbolUtil.createUnusedSymbolInfoBuilder(identifier, message, myDeadCodeInfoType, UnusedDeclarationInspectionBase.SHORT_NAME);
|
||||
for (IntentionAction fix : quickFixes) {
|
||||
TextRange fixRange = parent instanceof PsiField ? HighlightMethodUtil.getFixRange(parent) : null;
|
||||
builder.registerFix(fix, null, HighlightDisplayKey.getDisplayNameByKey(myDeadCodeKey), fixRange, myDeadCodeKey);
|
||||
}
|
||||
for (IntentionAction fix : quickFixOptions) {
|
||||
TextRange fixRange = parent instanceof PsiField ? HighlightMethodUtil.getFixRange(parent) : null;
|
||||
builder.registerFix(fix, null, HighlightDisplayKey.getDisplayNameByKey(myDeadCodeKey), fixRange, null);
|
||||
}
|
||||
addInfo(holder, builder);
|
||||
message = null;
|
||||
quickFixes.clear();
|
||||
quickFixOptions.clear();
|
||||
}
|
||||
else {
|
||||
assert quickFixes.isEmpty();
|
||||
assert quickFixOptions.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean compareVisibilities(PsiModifierListOwner listOwner, String visibility) {
|
||||
if (visibility != null) {
|
||||
while (listOwner != null) {
|
||||
if (VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(listOwner.getModifierList()), visibility) >= 0) {
|
||||
return true;
|
||||
}
|
||||
listOwner = PsiTreeUtil.getParentOfType(listOwner, PsiModifierListOwner.class, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processLocalVariable(@NotNull PsiLocalVariable variable) {
|
||||
if (variable.isUnnamed() || PsiUtil.isIgnoredName(variable.getName())) return;
|
||||
if (UnusedSymbolUtil.isImplicitUsage(myProject, variable)) return;
|
||||
|
||||
if (!myRefCountHolder.isReferenced(variable)) {
|
||||
message = JavaErrorBundle.message("local.variable.is.never.used", variable.getName());
|
||||
quickFixes.add(variable instanceof PsiResourceVariable ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable, false)
|
||||
: QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable));
|
||||
}
|
||||
|
||||
else if (!myRefCountHolder.isReferencedForRead(variable) && !UnusedSymbolUtil.isImplicitRead(myProject, variable)) {
|
||||
message = JavaErrorBundle.message("local.variable.is.not.used.for.reading", variable.getName());
|
||||
quickFixes.add(QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable));
|
||||
}
|
||||
|
||||
else if (!variable.hasInitializer() &&
|
||||
!myRefCountHolder.isReferencedForWrite(variable) &&
|
||||
!UnusedSymbolUtil.isImplicitWrite(myProject, variable)) {
|
||||
message = JavaErrorBundle.message("local.variable.is.not.assigned", variable.getName());
|
||||
quickFixes.add(QuickFixFactory.getInstance().createAddVariableInitializerFix(variable));
|
||||
}
|
||||
}
|
||||
|
||||
private void processField(@NotNull Project project, @NotNull PsiField field) {
|
||||
if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
|
||||
return;
|
||||
}
|
||||
if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
if (!myRefCountHolder.isReferenced(field) && !UnusedSymbolUtil.isImplicitUsage(myProject, field)) {
|
||||
message = JavaErrorBundle.message("private.field.is.not.used", field.getName());
|
||||
suggestionsToMakeFieldUsed(field);
|
||||
if (!field.hasInitializer() && !field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
quickFixes.add(QuickFixFactory.getInstance().createCreateConstructorParameterFromFieldFix(field));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
|
||||
if (!readReferenced && !UnusedSymbolUtil.isImplicitRead(project, field)) {
|
||||
message = getNotUsedForReadingMessage(field);
|
||||
suggestionsToMakeFieldUsed(field);
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.hasInitializer()) {
|
||||
return;
|
||||
}
|
||||
boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
|
||||
if (!writeReferenced && !UnusedSymbolUtil.isImplicitWrite(project, field)) {
|
||||
message = JavaErrorBundle.message("private.field.is.not.assigned", field.getName());
|
||||
|
||||
quickFixes.add(QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field));
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
quickFixes.add(QuickFixFactory.getInstance().createCreateConstructorParameterFromFieldFix(field));
|
||||
}
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(field, annoName ->
|
||||
quickFixes.add(QuickFixFactory.getInstance().createAddToImplicitlyWrittenFieldsFix(project, annoName)));
|
||||
}
|
||||
}
|
||||
else if (!UnusedSymbolUtil.isFieldUsed(myProject, myFile, field, myGlobalUsageHelper)) {
|
||||
if (UnusedSymbolUtil.isImplicitWrite(myProject, field)) {
|
||||
message = getNotUsedForReadingMessage(field);
|
||||
quickFixes.add(QuickFixFactory.getInstance().createSafeDeleteFix(field));
|
||||
}
|
||||
else if (!UnusedSymbolUtil.isImplicitUsage(myProject, field)) {
|
||||
formatUnusedSymbolHighlightInfo(project, "field.is.not.used", field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static @NlsContexts.DetailedDescription String getNotUsedForReadingMessage(@NotNull PsiField field) {
|
||||
String visibility = VisibilityUtil.getVisibilityStringToDisplay(field);
|
||||
String message = JavaErrorBundle.message("field.is.not.used.for.reading", visibility, field.getName());
|
||||
return StringUtil.capitalize(message);
|
||||
}
|
||||
|
||||
private void suggestionsToMakeFieldUsed(@NotNull PsiField field) {
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(field, annoName ->
|
||||
quickFixes.add(QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(field.getProject(), annoName)));
|
||||
quickFixes.add(QuickFixFactory.getInstance().createRemoveUnusedVariableFix(field));
|
||||
quickFixes.add(QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, false, field));
|
||||
quickFixes.add(QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field));
|
||||
quickFixes.add(QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, true, field));
|
||||
}
|
||||
|
||||
private final Map<PsiMethod, Boolean> isOverriddenOrOverrides = ConcurrentFactoryMap.createMap(method-> {
|
||||
boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
|
||||
return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
|
||||
}
|
||||
);
|
||||
|
||||
private boolean isOverriddenOrOverrides(@NotNull PsiMethod method) {
|
||||
return isOverriddenOrOverrides.get(method);
|
||||
}
|
||||
|
||||
private void processParameter(@NotNull Project project, @NotNull PsiParameter parameter) {
|
||||
if (parameter.isUnnamed() || PsiUtil.isIgnoredName(parameter.getName())) return;
|
||||
PsiElement declarationScope = parameter.getDeclarationScope();
|
||||
QuickFixFactory quickFixFactory = QuickFixFactory.getInstance();
|
||||
if (declarationScope instanceof PsiMethod method) {
|
||||
if (PsiUtilCore.hasErrorElementChild(method)) return;
|
||||
if ((method.isConstructor() ||
|
||||
method.hasModifierProperty(PsiModifier.PRIVATE) ||
|
||||
method.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
!method.hasModifierProperty(PsiModifier.ABSTRACT) &&
|
||||
(!isOverriddenOrOverrides(method) || myUnusedSymbolInspection.checkParameterExcludingHierarchy())) &&
|
||||
!method.hasModifierProperty(PsiModifier.NATIVE) &&
|
||||
!JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
|
||||
!isUsedMainOrPremainMethod(method)) {
|
||||
if (UnusedSymbolUtil.isInjected(project, method)) return;
|
||||
checkUnusedParameter(parameter, method);
|
||||
if (message != null) {
|
||||
quickFixes.add(quickFixFactory.createRenameToIgnoredFix(parameter, true));
|
||||
quickFixes.add(PriorityIntentionActionWrapper.highPriority(
|
||||
quickFixFactory.createSafeDeleteUnusedParameterInHierarchyFix(parameter,
|
||||
myUnusedSymbolInspection.checkParameterExcludingHierarchy() &&
|
||||
isOverriddenOrOverrides(method))));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (declarationScope instanceof PsiForeachStatement) {
|
||||
checkUnusedParameter(parameter, null);
|
||||
if (message != null) {
|
||||
quickFixes.add(quickFixFactory.createRenameToIgnoredFix(parameter, false));
|
||||
}
|
||||
}
|
||||
else if (parameter instanceof PsiPatternVariable variable) {
|
||||
checkUnusedParameter(parameter, null);
|
||||
if (message != null) {
|
||||
PsiPattern pattern = variable.getPattern();
|
||||
IntentionAction action = null;
|
||||
if (PsiUtil.isAvailable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES, parameter)) {
|
||||
if (pattern instanceof PsiTypeTestPattern ttPattern && pattern.getParent() instanceof PsiDeconstructionList) {
|
||||
PsiRecordComponent component = JavaPsiPatternUtil.getRecordComponentForPattern(pattern);
|
||||
PsiTypeElement checkType = ttPattern.getCheckType();
|
||||
if (component != null && checkType != null && checkType.getType().isAssignableFrom(component.getType())) {
|
||||
action = new ReplaceWithUnnamedPatternFix(pattern).asIntention();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action == null && declarationScope.getParent() instanceof PsiSwitchBlock) {
|
||||
action = variable.getParent() instanceof PsiDeconstructionPattern
|
||||
? quickFixFactory.createDeleteFix(parameter)
|
||||
: quickFixFactory.createRenameToIgnoredFix(parameter, false);
|
||||
}
|
||||
else if (!(pattern instanceof PsiTypeTestPattern && pattern.getParent() instanceof PsiDeconstructionList)) {
|
||||
action = quickFixFactory.createDeleteFix(parameter);
|
||||
}
|
||||
if (action != null) {
|
||||
quickFixOptions.add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((myUnusedSymbolInspection.checkParameterExcludingHierarchy() ||
|
||||
PsiUtil.isAvailable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES, declarationScope))
|
||||
&& declarationScope instanceof PsiLambdaExpression) {
|
||||
checkUnusedParameter(parameter, null);
|
||||
if (message != null) {
|
||||
quickFixes.add(quickFixFactory.createRenameToIgnoredFix(parameter, true));
|
||||
quickFixes.add(PriorityIntentionActionWrapper.lowPriority(quickFixFactory.createSafeDeleteUnusedParameterInHierarchyFix(parameter, true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isUsedMainOrPremainMethod(@NotNull PsiMethod method) {
|
||||
if (!PsiClassImplUtil.isMainOrPremainMethod(method)) {
|
||||
return false;
|
||||
}
|
||||
//premain
|
||||
if (!"main".equals(method.getName())) {
|
||||
return true;
|
||||
}
|
||||
if (!PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, method)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkUnusedParameter(@NotNull PsiParameter parameter, @Nullable PsiMethod declarationMethod) {
|
||||
if (!myRefCountHolder.isReferenced(parameter) && !UnusedSymbolUtil.isImplicitUsage(myProject, parameter)) {
|
||||
message = JavaErrorBundle.message(parameter instanceof PsiPatternVariable ?
|
||||
"pattern.variable.is.not.used" : "parameter.is.not.used", parameter.getName());
|
||||
if (declarationMethod != null) {
|
||||
IntentionAction assignFix = QuickFixFactory.getInstance().createAssignFieldFromParameterFix();
|
||||
IntentionAction createFieldFix = QuickFixFactory.getInstance().createCreateFieldFromParameterFix();
|
||||
if (!declarationMethod.isConstructor()) {
|
||||
assignFix = PriorityIntentionActionWrapper.lowPriority(assignFix);
|
||||
createFieldFix = PriorityIntentionActionWrapper.lowPriority(createFieldFix);
|
||||
}
|
||||
quickFixOptions.add(assignFix);
|
||||
quickFixOptions.add(createFieldFix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processMethod(@NotNull Project project, @NotNull PsiMethod method) {
|
||||
if (UnusedSymbolUtil.isMethodUsed(myProject, myFile, method, myGlobalUsageHelper)) {
|
||||
return;
|
||||
}
|
||||
String key;
|
||||
if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
|
||||
}
|
||||
else {
|
||||
key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
|
||||
}
|
||||
int options = PsiFormatUtilBase.SHOW_TYPE | PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES;
|
||||
String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY, options);
|
||||
message = JavaErrorBundle.message(key, symbolName);
|
||||
QuickFixFactory factory = QuickFixFactory.getInstance();
|
||||
quickFixes.add(factory.createSafeDeleteFix(method));
|
||||
if (ApplicationManager.getApplication().isHeadlessEnvironment() && method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
quickFixes.add(factory.createDeletePrivateMethodFix(method).asIntention());
|
||||
}
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(method, annoName ->
|
||||
quickFixes.add(factory.createAddToDependencyInjectionAnnotationsFix(project, annoName)));
|
||||
}
|
||||
|
||||
private void processClass(@NotNull Project project, @NotNull PsiClass aClass) {
|
||||
if (UnusedSymbolUtil.isClassUsed(project, myFile, aClass, myGlobalUsageHelper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String pattern;
|
||||
if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
pattern = aClass.isInterface()
|
||||
? "private.inner.interface.is.not.used"
|
||||
: "private.inner.class.is.not.used";
|
||||
}
|
||||
else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
|
||||
pattern = "local.class.is.not.used";
|
||||
}
|
||||
else if (aClass instanceof PsiTypeParameter) {
|
||||
pattern = "type.parameter.is.not.used";
|
||||
}
|
||||
else if (aClass.isInterface()) {
|
||||
pattern = "interface.is.not.used";
|
||||
}
|
||||
else if (aClass.isEnum()) {
|
||||
pattern = "enum.is.not.used";
|
||||
}
|
||||
else {
|
||||
pattern = "class.is.not.used";
|
||||
}
|
||||
formatUnusedSymbolHighlightInfo(myProject, pattern, aClass);
|
||||
}
|
||||
|
||||
|
||||
private void formatUnusedSymbolHighlightInfo(@NotNull Project project,
|
||||
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String pattern,
|
||||
@NotNull PsiMember member) {
|
||||
String symbolName = member.getName();
|
||||
message = JavaErrorBundle.message(pattern, symbolName);
|
||||
quickFixes.add(QuickFixFactory.getInstance().createSafeDeleteFix(member));
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(member, annoName ->
|
||||
quickFixes.add(QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName)));
|
||||
}
|
||||
|
||||
private void processImport(@NotNull HighlightInfoHolder holder,
|
||||
@NotNull PsiJavaFile javaFile,
|
||||
@@ -541,6 +127,22 @@ class PostHighlightingVisitor extends JavaElementVisitor {
|
||||
|
||||
if (PsiUtilCore.hasErrorElementChild(importStatement)) return;
|
||||
|
||||
boolean isRedundant = isRedundantImport(javaFile, importStatement);
|
||||
|
||||
if (isRedundant) {
|
||||
registerRedundantImport(holder, importStatement, unusedImportKey);
|
||||
return;
|
||||
}
|
||||
|
||||
int entryIndex = JavaCodeStyleManager.getInstance(myProject).findEntryIndex(importStatement);
|
||||
if (entryIndex < myCurrentEntryIndex && myOptimizeImportsFix == null) {
|
||||
// mis-sorted imports found
|
||||
myOptimizeImportsFix = QuickFixFactory.getInstance().createOptimizeImportsFix(true, myFile);
|
||||
}
|
||||
myCurrentEntryIndex = entryIndex;
|
||||
}
|
||||
|
||||
private boolean isRedundantImport(@NotNull PsiJavaFile javaFile, @NotNull PsiImportStatementBase importStatement) {
|
||||
boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
|
||||
if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
|
||||
// check import from the same package
|
||||
@@ -558,18 +160,7 @@ class PostHighlightingVisitor extends JavaElementVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isRedundant) {
|
||||
registerRedundantImport(holder, importStatement, unusedImportKey);
|
||||
return;
|
||||
}
|
||||
|
||||
int entryIndex = JavaCodeStyleManager.getInstance(myProject).findEntryIndex(importStatement);
|
||||
if (entryIndex < myCurrentEntryIndex && myOptimizeImportsFix == null) {
|
||||
// mis-sorted imports found
|
||||
myOptimizeImportsFix = QuickFixFactory.getInstance().createOptimizeImportsFix(true, myFile);
|
||||
}
|
||||
myCurrentEntryIndex = entryIndex;
|
||||
return isRedundant;
|
||||
}
|
||||
|
||||
private void registerRedundantImport(@NotNull HighlightInfoHolder holder,
|
||||
|
||||
@@ -1,33 +1,61 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInspection.unusedSymbol;
|
||||
|
||||
import com.intellij.codeInsight.daemon.JavaErrorBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.GlobalUsageHelper;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
|
||||
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
|
||||
import com.intellij.codeInspection.InspectionsBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.LocalRefUseInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceWithUnnamedPatternFix;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory;
|
||||
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
|
||||
import com.intellij.codeInspection.ex.UnfairLocalInspectionTool;
|
||||
import com.intellij.codeInspection.ex.EntryPointsManagerBase;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileImpl;
|
||||
import com.intellij.codeInspection.options.OptDropdown;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider;
|
||||
import com.intellij.codeInspection.util.InspectionMessage;
|
||||
import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.util.AccessModifier;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiClassImplUtil;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.search.searches.SuperMethodsSearch;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.VisibilityUtil;
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.*;
|
||||
|
||||
/**
|
||||
* Local counterpart of {@link com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase}
|
||||
*/
|
||||
public class UnusedSymbolLocalInspection extends AbstractBaseJavaLocalInspectionTool implements UnfairLocalInspectionTool {
|
||||
public final class UnusedSymbolLocalInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
@NonNls public static final String SHORT_NAME = HighlightInfoType.UNUSED_SYMBOL_SHORT_NAME;
|
||||
@NonNls public static final String UNUSED_PARAMETERS_SHORT_NAME = "UnusedParameters";
|
||||
@NonNls public static final String UNUSED_ID = "unused";
|
||||
@@ -36,17 +64,352 @@ public class UnusedSymbolLocalInspection extends AbstractBaseJavaLocalInspection
|
||||
public boolean FIELD = true;
|
||||
public boolean METHOD = true;
|
||||
public boolean CLASS = true;
|
||||
protected boolean INNER_CLASS = true;
|
||||
private boolean INNER_CLASS = true;
|
||||
public boolean PARAMETER = true;
|
||||
public boolean REPORT_PARAMETER_FOR_PUBLIC_METHODS = true;
|
||||
|
||||
protected String myClassVisibility = PsiModifier.PUBLIC;
|
||||
protected String myInnerClassVisibility = PsiModifier.PUBLIC;
|
||||
protected String myFieldVisibility = PsiModifier.PUBLIC;
|
||||
protected String myMethodVisibility = PsiModifier.PUBLIC;
|
||||
protected String myParameterVisibility = PsiModifier.PUBLIC;
|
||||
protected boolean myIgnoreAccessors = false;
|
||||
protected boolean myCheckParameterExcludingHierarchy = false;
|
||||
private String myClassVisibility = PsiModifier.PUBLIC;
|
||||
private String myInnerClassVisibility = PsiModifier.PUBLIC;
|
||||
private String myFieldVisibility = PsiModifier.PUBLIC;
|
||||
private String myMethodVisibility = PsiModifier.PUBLIC;
|
||||
private String myParameterVisibility = PsiModifier.PUBLIC;
|
||||
private boolean myIgnoreAccessors = false;
|
||||
private boolean myCheckParameterExcludingHierarchy = false;
|
||||
|
||||
@Override
|
||||
public boolean runForWholeFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
PsiFile file = holder.getFile();
|
||||
LocalRefUseInfo info = LocalRefUseInfo.forFile(file);
|
||||
Project project = holder.getProject();
|
||||
InspectionProfileImpl profile = InspectionProjectProfileManager.getInstance(project).getCurrentProfile();
|
||||
UnusedDeclarationInspectionBase deadCodeInspection = ObjectUtils.tryCast(
|
||||
profile.getUnwrappedTool(UnusedDeclarationInspectionBase.SHORT_NAME, file), UnusedDeclarationInspectionBase.class);
|
||||
GlobalUsageHelper helper = info.getGlobalUsageHelper(file, deadCodeInspection);
|
||||
return new JavaElementVisitor() {
|
||||
private final QuickFixFactory fixFactory = QuickFixFactory.getInstance();
|
||||
private final Map<PsiMethod, Boolean> isOverriddenOrOverrides = ConcurrentFactoryMap.createMap(
|
||||
method -> {
|
||||
boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
|
||||
return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
|
||||
}
|
||||
);
|
||||
|
||||
private boolean isOverriddenOrOverrides(@NotNull PsiMethod method) {
|
||||
return isOverriddenOrOverrides.get(method);
|
||||
}
|
||||
|
||||
private static boolean compareVisibilities(PsiModifierListOwner listOwner, String visibility) {
|
||||
if (visibility != null) {
|
||||
while (listOwner != null) {
|
||||
if (VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(listOwner.getModifierList()), visibility) >= 0) {
|
||||
return true;
|
||||
}
|
||||
listOwner = PsiTreeUtil.getParentOfType(listOwner, PsiModifierListOwner.class, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerProblem(@NotNull PsiElement element,
|
||||
@NotNull @InspectionMessage String message,
|
||||
@NotNull List<IntentionAction> fixes) {
|
||||
if (element instanceof PsiNameIdentifierOwner owner) {
|
||||
PsiElement identifier = owner.getNameIdentifier();
|
||||
if (identifier != null) {
|
||||
element = identifier;
|
||||
}
|
||||
}
|
||||
for (UnusedDeclarationFixProvider provider : UnusedDeclarationFixProvider.EP_NAME.getExtensionList()) {
|
||||
IntentionAction[] additionalFixes = provider.getQuickFixes(element);
|
||||
fixes = ContainerUtil.append(fixes, additionalFixes);
|
||||
}
|
||||
holder.registerProblem(element, message, ContainerUtil.map2Array(fixes, LocalQuickFix.EMPTY_ARRAY,
|
||||
UnusedSymbolLocalInspection::toLocalQuickFix));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
|
||||
if (!LOCAL_VARIABLE) return;
|
||||
if (variable.isUnnamed() || PsiUtil.isIgnoredName(variable.getName())) return;
|
||||
if (UnusedSymbolUtil.isImplicitUsage(project, variable)) return;
|
||||
|
||||
if (!info.isReferenced(variable)) {
|
||||
IntentionAction fix = variable instanceof PsiResourceVariable
|
||||
? fixFactory.createRenameToIgnoredFix(variable, false)
|
||||
: QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable);
|
||||
registerProblem(variable, JavaErrorBundle.message("local.variable.is.never.used", variable.getName()), List.of(fix));
|
||||
}
|
||||
|
||||
else if (!info.isReferencedForRead(variable) && !UnusedSymbolUtil.isImplicitRead(project, variable)) {
|
||||
registerProblem(variable, JavaErrorBundle.message("local.variable.is.not.used.for.reading", variable.getName()),
|
||||
List.of(QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable)));
|
||||
}
|
||||
|
||||
else if (!variable.hasInitializer() &&
|
||||
!info.isReferencedForWrite(variable) &&
|
||||
!UnusedSymbolUtil.isImplicitWrite(project, variable)) {
|
||||
registerProblem(variable, JavaErrorBundle.message("local.variable.is.not.assigned", variable.getName()),
|
||||
List.of(fixFactory.createAddVariableInitializerFix(variable)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
if (!compareVisibilities(field, getFieldVisibility())) return;
|
||||
if (HighlightUtil.isSerializationImplicitlyUsedField(field)) return;
|
||||
if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
if (!info.isReferenced(field) && !UnusedSymbolUtil.isImplicitUsage(project, field)) {
|
||||
List<IntentionAction> fixes = new ArrayList<>(suggestionsToMakeFieldUsed(field));
|
||||
if (!field.hasInitializer() && !field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
fixes.add(fixFactory.createCreateConstructorParameterFromFieldFix(field));
|
||||
}
|
||||
registerProblem(field, JavaErrorBundle.message("private.field.is.not.used", field.getName()), fixes);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean readReferenced = info.isReferencedForRead(field);
|
||||
if (!readReferenced && !UnusedSymbolUtil.isImplicitRead(project, field)) {
|
||||
registerProblem(field, getNotUsedForReadingMessage(field), suggestionsToMakeFieldUsed(field));
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.hasInitializer()) {
|
||||
return;
|
||||
}
|
||||
boolean writeReferenced = info.isReferencedForWrite(field);
|
||||
if (!writeReferenced && !UnusedSymbolUtil.isImplicitWrite(project, field)) {
|
||||
List<IntentionAction> fixes = new ArrayList<>();
|
||||
fixes.add(fixFactory.createCreateGetterOrSetterFix(false, true, field));
|
||||
if (!field.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
fixes.add(fixFactory.createCreateConstructorParameterFromFieldFix(field));
|
||||
}
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(field, annoName ->
|
||||
fixes.add(fixFactory.createAddToImplicitlyWrittenFieldsFix(project, annoName)));
|
||||
registerProblem(field, JavaErrorBundle.message("private.field.is.not.assigned", field.getName()), fixes);
|
||||
}
|
||||
}
|
||||
else if (!UnusedSymbolUtil.isFieldUsed(project, file, field, helper)) {
|
||||
if (UnusedSymbolUtil.isImplicitWrite(project, field)) {
|
||||
registerProblem(field, getNotUsedForReadingMessage(field), List.of(fixFactory.createSafeDeleteFix(field)));
|
||||
}
|
||||
else if (!UnusedSymbolUtil.isImplicitUsage(project, field)) {
|
||||
formatUnusedSymbolHighlightInfo("field.is.not.used", field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void formatUnusedSymbolHighlightInfo(@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String pattern,
|
||||
@NotNull PsiMember member) {
|
||||
List<IntentionAction> fixes = new ArrayList<>();
|
||||
fixes.add(QuickFixFactory.getInstance().createSafeDeleteFix(member));
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(member, annoName ->
|
||||
fixes.add(QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName)));
|
||||
registerProblem(member, JavaErrorBundle.message(pattern, member.getName()), fixes);
|
||||
}
|
||||
|
||||
private static boolean isUsedMainOrPremainMethod(@NotNull PsiMethod method) {
|
||||
if (!PsiClassImplUtil.isMainOrPremainMethod(method)) {
|
||||
return false;
|
||||
}
|
||||
//premain
|
||||
if (!"main".equals(method.getName())) {
|
||||
return true;
|
||||
}
|
||||
return !PsiUtil.isAvailable(JavaFeature.IMPLICIT_CLASSES, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
if (isIgnoreAccessors() && PropertyUtilBase.isSimplePropertyAccessor(method)) return;
|
||||
if (!compareVisibilities(method, getMethodVisibility())) return;
|
||||
if (UnusedSymbolUtil.isMethodUsed(project, file, method, helper)) return;
|
||||
String key;
|
||||
if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
|
||||
}
|
||||
else {
|
||||
key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
|
||||
}
|
||||
int options = PsiFormatUtilBase.SHOW_TYPE | PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES;
|
||||
String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY, options);
|
||||
QuickFixFactory factory = QuickFixFactory.getInstance();
|
||||
List<IntentionAction> fixes = new ArrayList<>();
|
||||
fixes.add(factory.createSafeDeleteFix(method));
|
||||
if (ApplicationManager.getApplication().isHeadlessEnvironment() && method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
fixes.add(factory.createDeletePrivateMethodFix(method).asIntention());
|
||||
}
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(method, annoName ->
|
||||
fixes.add(factory.createAddToDependencyInjectionAnnotationsFix(project, annoName)));
|
||||
registerProblem(method, JavaErrorBundle.message(key, symbolName), fixes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitParameter(@NotNull PsiParameter parameter) {
|
||||
PsiElement declarationScope = parameter.getDeclarationScope();
|
||||
boolean needToProcessParameter;
|
||||
if (declarationScope instanceof PsiMethod method) {
|
||||
needToProcessParameter = compareVisibilities(method, getParameterVisibility());
|
||||
}
|
||||
else if (declarationScope instanceof PsiLambdaExpression) {
|
||||
needToProcessParameter = compareVisibilities(
|
||||
PsiTreeUtil.getParentOfType(declarationScope, PsiModifierListOwner.class), getParameterVisibility());
|
||||
}
|
||||
else {
|
||||
needToProcessParameter = LOCAL_VARIABLE;
|
||||
}
|
||||
if (!needToProcessParameter) return;
|
||||
if (parameter.isUnnamed() || PsiUtil.isIgnoredName(parameter.getName())) return;
|
||||
QuickFixFactory quickFixFactory = QuickFixFactory.getInstance();
|
||||
if (declarationScope instanceof PsiMethod method) {
|
||||
if (PsiUtilCore.hasErrorElementChild(method)) return;
|
||||
if ((method.isConstructor() ||
|
||||
method.hasModifierProperty(PsiModifier.PRIVATE) ||
|
||||
method.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
!method.hasModifierProperty(PsiModifier.ABSTRACT) &&
|
||||
(!isOverriddenOrOverrides(method) || checkParameterExcludingHierarchy())) &&
|
||||
!method.hasModifierProperty(PsiModifier.NATIVE) &&
|
||||
!JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
|
||||
!isUsedMainOrPremainMethod(method)) {
|
||||
if (UnusedSymbolUtil.isInjected(project, method)) return;
|
||||
String message = checkUnusedParameter(parameter);
|
||||
if (message != null) {
|
||||
registerProblem(
|
||||
parameter, message,
|
||||
ContainerUtil.append(
|
||||
getFixesForUnusedParameter(method),
|
||||
quickFixFactory.createRenameToIgnoredFix(parameter, true),
|
||||
PriorityIntentionActionWrapper.highPriority(quickFixFactory.createSafeDeleteUnusedParameterInHierarchyFix(
|
||||
parameter, checkParameterExcludingHierarchy() && isOverriddenOrOverrides(method)))));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (declarationScope instanceof PsiForeachStatement) {
|
||||
String message = checkUnusedParameter(parameter);
|
||||
if (message != null) {
|
||||
registerProblem(parameter, message, List.of(quickFixFactory.createRenameToIgnoredFix(parameter, false)));
|
||||
}
|
||||
}
|
||||
else if (parameter instanceof PsiPatternVariable variable) {
|
||||
String message = checkUnusedParameter(parameter);
|
||||
if (message != null) {
|
||||
PsiPattern pattern = variable.getPattern();
|
||||
IntentionAction action = null;
|
||||
if (PsiUtil.isAvailable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES, parameter)) {
|
||||
if (pattern instanceof PsiTypeTestPattern ttPattern && pattern.getParent() instanceof PsiDeconstructionList) {
|
||||
PsiRecordComponent component = JavaPsiPatternUtil.getRecordComponentForPattern(pattern);
|
||||
PsiTypeElement checkType = ttPattern.getCheckType();
|
||||
if (component != null && checkType != null && checkType.getType().isAssignableFrom(component.getType())) {
|
||||
action = new ReplaceWithUnnamedPatternFix(pattern).asIntention();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action == null && declarationScope.getParent() instanceof PsiSwitchBlock) {
|
||||
action = variable.getParent() instanceof PsiDeconstructionPattern
|
||||
? quickFixFactory.createDeleteFix(parameter)
|
||||
: quickFixFactory.createRenameToIgnoredFix(parameter, false);
|
||||
}
|
||||
else if (!(pattern instanceof PsiTypeTestPattern && pattern.getParent() instanceof PsiDeconstructionList)) {
|
||||
action = quickFixFactory.createDeleteFix(parameter);
|
||||
}
|
||||
registerProblem(parameter, message, ContainerUtil.createMaybeSingletonList(action));
|
||||
}
|
||||
}
|
||||
else if ((checkParameterExcludingHierarchy() ||
|
||||
PsiUtil.isAvailable(JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES, declarationScope))
|
||||
&& declarationScope instanceof PsiLambdaExpression) {
|
||||
String message = checkUnusedParameter(parameter);
|
||||
if (message != null) {
|
||||
registerProblem(
|
||||
parameter, message,
|
||||
List.of(quickFixFactory.createRenameToIgnoredFix(parameter, true),
|
||||
PriorityIntentionActionWrapper.lowPriority(
|
||||
quickFixFactory.createSafeDeleteUnusedParameterInHierarchyFix(parameter, true))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable @InspectionMessage String checkUnusedParameter(@NotNull PsiParameter parameter) {
|
||||
if (!info.isReferenced(parameter) && !UnusedSymbolUtil.isImplicitUsage(project, parameter)) {
|
||||
return JavaErrorBundle.message(parameter instanceof PsiPatternVariable ?
|
||||
"pattern.variable.is.not.used" : "parameter.is.not.used", parameter.getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @NotNull List<IntentionAction> getFixesForUnusedParameter(@Nullable PsiMethod declarationMethod) {
|
||||
if (declarationMethod != null) {
|
||||
IntentionAction assignFix = QuickFixFactory.getInstance().createAssignFieldFromParameterFix();
|
||||
IntentionAction createFieldFix = QuickFixFactory.getInstance().createCreateFieldFromParameterFix();
|
||||
if (!declarationMethod.isConstructor()) {
|
||||
assignFix = PriorityIntentionActionWrapper.lowPriority(assignFix);
|
||||
createFieldFix = PriorityIntentionActionWrapper.lowPriority(createFieldFix);
|
||||
}
|
||||
return List.of(assignFix, createFieldFix);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
String acceptedVisibility = aClass.getContainingClass() == null ? getClassVisibility()
|
||||
: getInnerClassVisibility();
|
||||
if (!compareVisibilities(aClass, acceptedVisibility)) return;
|
||||
if (UnusedSymbolUtil.isClassUsed(project, file, aClass, helper)) return;
|
||||
|
||||
String pattern;
|
||||
if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
pattern = aClass.isInterface()
|
||||
? "private.inner.interface.is.not.used"
|
||||
: "private.inner.class.is.not.used";
|
||||
}
|
||||
else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
|
||||
pattern = "local.class.is.not.used";
|
||||
}
|
||||
else if (aClass instanceof PsiTypeParameter) {
|
||||
pattern = "type.parameter.is.not.used";
|
||||
}
|
||||
else if (aClass.isInterface()) {
|
||||
pattern = "interface.is.not.used";
|
||||
}
|
||||
else if (aClass.isEnum()) {
|
||||
pattern = "enum.is.not.used";
|
||||
}
|
||||
else {
|
||||
pattern = "class.is.not.used";
|
||||
}
|
||||
formatUnusedSymbolHighlightInfo(pattern, aClass);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static @NlsContexts.DetailedDescription String getNotUsedForReadingMessage(@NotNull PsiField field) {
|
||||
String visibility = VisibilityUtil.getVisibilityStringToDisplay(field);
|
||||
String message = JavaErrorBundle.message("field.is.not.used.for.reading", visibility, field.getName());
|
||||
return StringUtil.capitalize(message);
|
||||
}
|
||||
|
||||
private List<IntentionAction> suggestionsToMakeFieldUsed(@NotNull PsiField field) {
|
||||
List<IntentionAction> quickFixes = new ArrayList<>();
|
||||
SpecialAnnotationsUtilBase.processUnknownAnnotations(field, annoName ->
|
||||
quickFixes.add(EntryPointsManagerBase.createAddEntryPointAnnotation(annoName).asIntention()));
|
||||
quickFixes.add(QuickFixFactory.getInstance().createRemoveUnusedVariableFix(field));
|
||||
quickFixes.add(fixFactory.createCreateGetterOrSetterFix(true, false, field));
|
||||
quickFixes.add(fixFactory.createCreateGetterOrSetterFix(false, true, field));
|
||||
quickFixes.add(fixFactory.createCreateGetterOrSetterFix(true, true, field));
|
||||
return quickFixes;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static @NotNull LocalQuickFix toLocalQuickFix(IntentionAction fix) {
|
||||
ModCommandAction action = fix.asModCommandAction();
|
||||
return action == null ? new LocalQuickFixBackedByIntentionAction(fix) :
|
||||
LocalQuickFix.from(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
@@ -110,27 +473,18 @@ public class UnusedSymbolLocalInspection extends AbstractBaseJavaLocalInspection
|
||||
return myInnerClassVisibility;
|
||||
}
|
||||
|
||||
public void setInnerClassVisibility(String innerClassVisibility) {
|
||||
myInnerClassVisibility = innerClassVisibility;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setClassVisibility(String classVisibility) {
|
||||
this.myClassVisibility = classVisibility;
|
||||
}
|
||||
|
||||
public void setFieldVisibility(String fieldVisibility) {
|
||||
this.myFieldVisibility = fieldVisibility;
|
||||
}
|
||||
|
||||
public void setMethodVisibility(String methodVisibility) {
|
||||
this.myMethodVisibility = methodVisibility;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setParameterVisibility(String parameterVisibility) {
|
||||
REPORT_PARAMETER_FOR_PUBLIC_METHODS = PsiModifier.PUBLIC.equals(parameterVisibility);
|
||||
this.myParameterVisibility = parameterVisibility;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setCheckParameterExcludingHierarchy(boolean checkParameterExcludingHierarchy) {
|
||||
this.myCheckParameterExcludingHierarchy = checkParameterExcludingHierarchy;
|
||||
}
|
||||
@@ -139,10 +493,6 @@ public class UnusedSymbolLocalInspection extends AbstractBaseJavaLocalInspection
|
||||
return myIgnoreAccessors;
|
||||
}
|
||||
|
||||
public void setIgnoreAccessors(boolean ignoreAccessors) {
|
||||
myIgnoreAccessors = ignoreAccessors;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getGroupDisplayName() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Apply all 'Replace with unnamed pattern' fixes in file" "true"
|
||||
// "Fix all 'Unused declaration' problems in file" "true"
|
||||
public class Test {
|
||||
record R(int x, int y) {}
|
||||
void test(Object obj) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Apply all 'Replace with unnamed pattern' fixes in file" "true"
|
||||
// "Fix all 'Unused declaration' problems in file" "true"
|
||||
public class Test {
|
||||
record R(int x, int y) {}
|
||||
void test(Object obj) {
|
||||
|
||||
@@ -134,17 +134,17 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
|
||||
-f()
|
||||
-D
|
||||
-b()
|
||||
Variable 'r' is never used.
|
||||
Variable 'r' is never used
|
||||
-anonymous (Runnable)
|
||||
-run()
|
||||
Variable 'i' is never used.
|
||||
Variable 'i' is never used
|
||||
-ff()
|
||||
Variable 'a' is never used.
|
||||
Variable 'd' is never used.
|
||||
Variable 'd' is never used
|
||||
Variable 'a' is never used
|
||||
-foo()
|
||||
Variable 'j' is never used.
|
||||
Variable 'j' is never used
|
||||
-main(String[])
|
||||
Variable 'test' is never used.
|
||||
Variable 'test' is never used
|
||||
-Probable bugs
|
||||
-'equals()' called on itself
|
||||
-testOfflineWithInvalid
|
||||
@@ -179,17 +179,17 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
|
||||
-f()
|
||||
-D
|
||||
-b()
|
||||
Variable 'r' is never used.
|
||||
Variable 'r' is never used
|
||||
-anonymous (Runnable)
|
||||
-run()
|
||||
Variable 'i' is never used.
|
||||
Variable 'i' is never used
|
||||
-ff()
|
||||
Variable 'a' is never used.
|
||||
Variable 'd' is never used.
|
||||
Variable 'd' is never used
|
||||
Variable 'a' is never used
|
||||
-foo()
|
||||
Variable 'j' is never used.
|
||||
Variable 'j' is never used
|
||||
-main(String[])
|
||||
Variable 'test' is never used.
|
||||
Variable 'test' is never used
|
||||
-Probable bugs
|
||||
-'equals()' called on itself
|
||||
-testOfflineView
|
||||
@@ -217,17 +217,17 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
|
||||
-f()
|
||||
-D
|
||||
-b()
|
||||
Variable 'r' is never used.
|
||||
Variable 'r' is never used
|
||||
-anonymous (Runnable)
|
||||
-run()
|
||||
Variable 'i' is never used.
|
||||
Variable 'i' is never used
|
||||
-ff()
|
||||
Variable 'a' is never used.
|
||||
Variable 'd' is never used.
|
||||
Variable 'd' is never used
|
||||
Variable 'a' is never used
|
||||
-foo()
|
||||
Variable 'j' is never used.
|
||||
Variable 'j' is never used
|
||||
-main(String[])
|
||||
Variable 'test' is never used.
|
||||
Variable 'test' is never used
|
||||
-Probable bugs
|
||||
-'equals()' called on itself
|
||||
-Test
|
||||
@@ -251,17 +251,17 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
|
||||
-f()
|
||||
-D
|
||||
-b()
|
||||
Variable 'r' is never used.
|
||||
Variable 'r' is never used
|
||||
-anonymous (Runnable)
|
||||
-run()
|
||||
Variable 'i' is never used.
|
||||
Variable 'i' is never used
|
||||
-ff()
|
||||
Variable 'a' is never used.
|
||||
Variable 'd' is never used.
|
||||
Variable 'd' is never used
|
||||
Variable 'a' is never used
|
||||
-foo()
|
||||
Variable 'j' is never used.
|
||||
Variable 'j' is never used
|
||||
-main(String[])
|
||||
Variable 'test' is never used.
|
||||
Variable 'test' is never used
|
||||
-Probable bugs
|
||||
-'equals()' called on itself
|
||||
-Test
|
||||
|
||||
@@ -798,12 +798,6 @@ public class HighlightInfo implements Segment {
|
||||
List<IntentionAction> newOptions = intentionManager.getStandardIntentionOptions(key, element);
|
||||
InspectionProfile profile = InspectionProjectProfileManager.getInstance(element.getProject()).getCurrentProfile();
|
||||
InspectionToolWrapper<?, ?> toolWrapper = profile.getInspectionTool(key.toString(), element);
|
||||
if (!(toolWrapper instanceof LocalInspectionToolWrapper)) {
|
||||
HighlightDisplayKey idKey = HighlightDisplayKey.findById(key.toString());
|
||||
if (idKey != null) {
|
||||
toolWrapper = profile.getInspectionTool(idKey.toString(), element);
|
||||
}
|
||||
}
|
||||
if (toolWrapper != null) {
|
||||
myCanCleanup = toolWrapper.isCleanupTool();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user