From 4590cb7fea4ed71b98978374809c96087788fdc0 Mon Sep 17 00:00:00 2001 From: Ilyas Selimov Date: Fri, 3 Sep 2021 14:47:25 +0700 Subject: [PATCH] IDEA-169223 - added an option to be able to highlight unused parameters apart from hierarchy and quick fix to replace parameter name to "ignored" GitOrigin-RevId: 4ffc252043c5c1532ec123c146e199e5ce950328 --- .../intention/QuickFixFactory.java | 6 +- .../analysis/PostHighlightingVisitor.java | 41 ++++++++--- .../SetInspectionOptionFix.java | 32 ++++++++- .../UnusedSymbolLocalInspectionBase.java | 15 +++- .../impl/quickfix/RenameToIgnoredFix.java | 21 +++++- .../impl/config/QuickFixFactoryImpl.java | 41 +++++++++-- .../unusedSymbol/OptionsPanel.form | 9 +++ .../UnusedSymbolLocalInspection.java | 14 +++- .../src/com/intellij/psi/util/PsiUtil.java | 2 +- .../src/messages/JavaErrorBundle.properties | 1 + .../ExcludingHierarchyInAnonymousClass.java | 14 ++++ .../ExcludingHierarchyInLambda.java | 10 +++ .../ExcludingHierarchyInOverriddenMethod.java | 9 +++ .../ExcludingHierarchyInPlainMethod.java | 4 ++ .../IncludingHierarchy.java | 28 ++++++++ .../UnusedParameterInHierarchyTest.java | 68 +++++++++++++++++++ .../resources/messages/JavaBundle.properties | 1 + 17 files changed, 288 insertions(+), 28 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInAnonymousClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInLambda.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInOverriddenMethod.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInPlainMethod.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/IncludingHierarchy.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/UnusedParameterInHierarchyTest.java diff --git a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java index f9f20269b948..b128dbfd80f6 100644 --- a/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java +++ b/java/java-analysis-api/src/com/intellij/codeInsight/intention/QuickFixFactory.java @@ -390,7 +390,9 @@ public abstract class QuickFixFactory { @NotNull public abstract IntentionAction createOptimizeImportsFix(boolean onTheFly); - public abstract void registerFixesForUnusedParameter(@NotNull PsiParameter parameter, @NotNull Object highlightInfo); + public abstract void registerFixesForUnusedParameter(@NotNull PsiParameter parameter, + @NotNull Object highlightInfo, + boolean excludingHierarchy); @NotNull public abstract IntentionAction createAddToDependencyInjectionAnnotationsFix(@NotNull Project project, @NotNull String qualifiedName); @@ -402,7 +404,7 @@ public abstract class QuickFixFactory { public abstract IntentionAction createCreateGetterOrSetterFix(boolean createGetter, boolean createSetter, @NotNull PsiField field); @NotNull - public abstract IntentionAction createRenameToIgnoredFix(@NotNull PsiNamedElement namedElement); + public abstract IntentionAction createRenameToIgnoredFix(@NotNull PsiNamedElement namedElement, boolean useElementNameAsSuffix); @NotNull public abstract IntentionAction createEnableOptimizeImportsOnTheFlyFix(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java index 8db2ebdf4236..e8281c3c5b27 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java @@ -205,9 +205,18 @@ class PostHighlightingVisitor { return processField(myProject, (PsiField)parent, identifier, progress, helper); } if (parent instanceof PsiParameter) { - final PsiElement declarationScope = ((PsiParameter)parent).getDeclarationScope(); - if (declarationScope instanceof PsiMethod ? compareVisibilities((PsiModifierListOwner)declarationScope, myUnusedSymbolInspection.getParameterVisibility()) - : myUnusedSymbolInspection.LOCAL_VARIABLE) { + PsiElement declarationScope = ((PsiParameter)parent).getDeclarationScope(); + final 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) { if (SuppressionUtil.isSuppressed(identifier, UnusedSymbolLocalInspectionBase.UNUSED_PARAMETERS_SHORT_NAME)) return null; return processParameter(myProject, (PsiParameter)parent, identifier); } @@ -252,7 +261,7 @@ class PostHighlightingVisitor { IntentionAction fix = null; if (!myRefCountHolder.isReferenced(variable)) { message = JavaErrorBundle.message("local.variable.is.never.used", identifier.getText()); - fix = variable instanceof PsiResourceVariable ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable) + fix = variable instanceof PsiResourceVariable ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable, false) : QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable); } @@ -379,6 +388,7 @@ class PostHighlightingVisitor { private HighlightInfo processParameter(@NotNull Project project, @NotNull PsiParameter parameter, @NotNull PsiIdentifier identifier) { + if (PsiUtil.isIgnoredName(parameter.getName())) return null; PsiElement declarationScope = parameter.getDeclarationScope(); if (declarationScope instanceof PsiMethod) { PsiMethod method = (PsiMethod)declarationScope; @@ -387,30 +397,32 @@ class PostHighlightingVisitor { method.hasModifierProperty(PsiModifier.PRIVATE) || method.hasModifierProperty(PsiModifier.STATIC) || !method.hasModifierProperty(PsiModifier.ABSTRACT) && - !isOverriddenOrOverrides(method)) && + (!isOverriddenOrOverrides(method) || myUnusedSymbolInspection.checkParameterExcludingHierarchy())) && !method.hasModifierProperty(PsiModifier.NATIVE) && !JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) && !PsiClassImplUtil.isMainOrPremainMethod(method)) { if (UnusedSymbolUtil.isInjected(project, method)) return null; HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, method); if (highlightInfo != null) { - QuickFixFactory.getInstance().registerFixesForUnusedParameter(parameter, highlightInfo); + QuickFixFactory.getInstance() + .registerFixesForUnusedParameter(parameter, highlightInfo, myUnusedSymbolInspection.checkParameterExcludingHierarchy() && + isOverriddenOrOverrides(method)); return highlightInfo; } } } - else if (declarationScope instanceof PsiForeachStatement && !PsiUtil.isIgnoredName(parameter.getName())) { + else if (declarationScope instanceof PsiForeachStatement) { HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, null); if (highlightInfo != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter), myDeadCodeKey); + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter, false), myDeadCodeKey); return highlightInfo; } } - else if (parameter instanceof PsiPatternVariable && !PsiUtil.isIgnoredName(parameter.getName())) { + else if (parameter instanceof PsiPatternVariable) { HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, null); if (highlightInfo != null) { - if (parameter.getDeclarationScope().getParent() instanceof PsiSwitchBlock) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter)); + if (declarationScope.getParent() instanceof PsiSwitchBlock) { + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter, false)); } else { QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteFix(parameter)); @@ -418,6 +430,13 @@ class PostHighlightingVisitor { return highlightInfo; } } + else if (myUnusedSymbolInspection.checkParameterExcludingHierarchy() && declarationScope instanceof PsiLambdaExpression) { + HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, null); + if (highlightInfo != null) { + QuickFixFactory.getInstance().registerFixesForUnusedParameter(parameter, highlightInfo, true); + return highlightInfo; + } + } return null; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/SetInspectionOptionFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/SetInspectionOptionFix.java index b37ddb823ca1..2f22fb2dfecc 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/SetInspectionOptionFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/SetInspectionOptionFix.java @@ -17,20 +17,39 @@ import com.intellij.psi.PsiManager; import com.intellij.util.ReflectionUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.function.Function; public class SetInspectionOptionFix implements OnTheFlyLocalFix, LowPriorityAction, Iconable { private final String myID; private final String myProperty; private final @IntentionName String myMessage; private final boolean myValue; + @Nullable + private final Function myExtractor; public SetInspectionOptionFix(LocalInspectionTool inspection, @NonNls String property, @IntentionName String message, boolean value) { - myID = inspection.getID(); + this(inspection.getID(), property, message, value, null); + } + + private SetInspectionOptionFix(@NotNull String id, @NonNls String property, @IntentionName String message, boolean value, + @Nullable Function extractor) { + myID = id; myProperty = property; myMessage = message; myValue = value; + myExtractor = extractor; + } + + /** + * @param extractor may be useful for composed inspections e.g. unused declaration, when you need to unwrap a nested inspection's instance + */ + @NotNull + public static SetInspectionOptionFix createFix(@NotNull String id, @NonNls String property, @IntentionName String message, boolean value, + @NotNull Function extractor) { + return new SetInspectionOptionFix(id, property, message, value, extractor); } @NotNull @@ -52,7 +71,11 @@ public class SetInspectionOptionFix implements OnTheFlyLocalFix, LowPriorityActi @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - VirtualFile vFile = descriptor.getPsiElement().getContainingFile().getVirtualFile(); + applyFix(project, descriptor.getPsiElement().getContainingFile()); + } + + public void applyFix(@NotNull Project project, @NotNull PsiFile psiFile) { + VirtualFile vFile = psiFile.getVirtualFile(); setOption(project, vFile, myValue); UndoManager.getInstance(project).undoableActionPerformed(new BasicUndoableAction(vFile) { @Override @@ -72,8 +95,11 @@ public class SetInspectionOptionFix implements OnTheFlyLocalFix, LowPriorityActi if (file == null) return; InspectionProfileModifiableModelKt.modifyAndCommitProjectProfile(project, model -> { InspectionToolWrapper tool = model.getToolById(myID, file); - if(tool == null) return; + if (tool == null) return; InspectionProfileEntry inspection = tool.getTool(); + if (myExtractor != null) { + inspection = myExtractor.apply(inspection); + } ReflectionUtil.setField(inspection.getClass(), inspection, boolean.class, myProperty, value); }); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java index 69d1632004e8..7850acdc1da3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java @@ -34,7 +34,7 @@ public class UnusedSymbolLocalInspectionBase extends AbstractBaseJavaLocalInspec protected String myMethodVisibility = PsiModifier.PUBLIC; protected String myParameterVisibility = PsiModifier.PUBLIC; private boolean myIgnoreAccessors = false; - + protected boolean myCheckParameterExcludingHierarchy = false; @PsiModifier.ModifierConstant @Nullable @@ -62,6 +62,10 @@ public class UnusedSymbolLocalInspectionBase extends AbstractBaseJavaLocalInspec return myParameterVisibility; } + public boolean checkParameterExcludingHierarchy() { + return myCheckParameterExcludingHierarchy; + } + @PsiModifier.ModifierConstant @Nullable public String getInnerClassVisibility() { @@ -90,6 +94,10 @@ public class UnusedSymbolLocalInspectionBase extends AbstractBaseJavaLocalInspec this.myParameterVisibility = parameterVisibility; } + public void setCheckParameterExcludingHierarchy(boolean checkParameterExcludingHierarchy) { + this.myCheckParameterExcludingHierarchy = checkParameterExcludingHierarchy; + } + public boolean isIgnoreAccessors() { return myIgnoreAccessors; } @@ -142,6 +150,7 @@ public class UnusedSymbolLocalInspectionBase extends AbstractBaseJavaLocalInspec if (!INNER_CLASS) { node.setAttribute("INNER_CLASS", Boolean.toString(false)); } + node.setAttribute("checkParameterExcludingHierarchy", Boolean.toString(myCheckParameterExcludingHierarchy)); super.writeSettings(node); } @@ -171,9 +180,11 @@ public class UnusedSymbolLocalInspectionBase extends AbstractBaseJavaLocalInspec myMethodVisibility = readVisibility(node, "method"); myParameterVisibility = readVisibility(node, "parameter", getParameterDefaultVisibility()); final String ignoreAccessors = node.getAttributeValue("ignoreAccessors"); - myIgnoreAccessors = ignoreAccessors != null && Boolean.parseBoolean(ignoreAccessors); + myIgnoreAccessors = Boolean.parseBoolean(ignoreAccessors); final String innerClassEnabled = node.getAttributeValue("INNER_CLASS"); INNER_CLASS = innerClassEnabled == null || Boolean.parseBoolean(innerClassEnabled); + final String checkParameterExcludingHierarchy = node.getAttributeValue("checkParameterExcludingHierarchy"); + myCheckParameterExcludingHierarchy = Boolean.parseBoolean(checkParameterExcludingHierarchy); } private static String readVisibility(@NotNull Element node, final String type) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RenameToIgnoredFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RenameToIgnoredFix.java index 7535f0121be4..1cb5f7a83299 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RenameToIgnoredFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RenameToIgnoredFix.java @@ -15,12 +15,29 @@ */ package com.intellij.codeInsight.daemon.impl.quickfix; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import org.jetbrains.annotations.NotNull; public class RenameToIgnoredFix extends RenameElementFix { - public RenameToIgnoredFix(@NotNull PsiNamedElement element) { - super(element, JavaCodeStyleManager.getInstance(element.getProject()).suggestUniqueVariableName("ignored", element, true)); + private static final String PREFIX = "ignored"; + + private RenameToIgnoredFix(@NotNull PsiNamedElement place, @NotNull String suffix) { + super(place, JavaCodeStyleManager.getInstance(place.getProject()).suggestUniqueVariableName(PREFIX + suffix, place, true)); + } + + /** + * @param useElementNameAsSuffix if true, let the fix suggest the variable name that consists of the "ignored" and name of the element + * e.g. ignoredVar + *

if false, let the fix suggest the variable that consists of the "ignored" and some number + * e.g. ignored1 + */ + public static RenameToIgnoredFix createRenameToIgnoreFix(@NotNull PsiNamedElement element, boolean useElementNameAsSuffix) { + if (useElementNameAsSuffix) { + String elementName = element.getName(); + if (elementName != null) return new RenameToIgnoredFix(element, StringUtil.capitalize(elementName)); + } + return new RenameToIgnoredFix(element, ""); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java index 4ef9860be8c9..0c35203ff3b0 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/config/QuickFixFactoryImpl.java @@ -4,6 +4,7 @@ package com.intellij.codeInsight.intention.impl.config; import com.intellij.codeInsight.CodeInsightWorkspaceSettings; import com.intellij.codeInsight.actions.OptimizeImportsProcessor; import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInsight.daemon.JavaErrorBundle; import com.intellij.codeInsight.daemon.QuickFixActionRegistrar; import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.daemon.impl.*; @@ -11,6 +12,7 @@ import com.intellij.codeInsight.daemon.impl.analysis.IncreaseLanguageLevelFix; import com.intellij.codeInsight.daemon.impl.quickfix.*; import com.intellij.codeInsight.daemon.quickFix.CreateClassOrPackageFix; import com.intellij.codeInsight.daemon.quickFix.CreateFieldOrPropertyFix; +import com.intellij.codeInsight.intention.AbstractIntentionAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.codeInsight.intention.QuickFixFactory; @@ -18,6 +20,7 @@ import com.intellij.codeInsight.intention.impl.*; import com.intellij.codeInspection.*; import com.intellij.codeInspection.actions.UnimplementInterfaceAction; import com.intellij.codeInspection.dataFlow.fix.DeleteSwitchLabelFix; +import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase; import com.intellij.codeInspection.ex.EntryPointsManagerBase; import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase; import com.intellij.codeInspection.util.IntentionName; @@ -691,7 +694,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory { } @Override - public void registerFixesForUnusedParameter(@NotNull PsiParameter parameter, @NotNull Object highlightInfo) { + public void registerFixesForUnusedParameter(@NotNull PsiParameter parameter, @NotNull Object highlightInfo, boolean excludingHierarchy) { Project myProject = parameter.getProject(); InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile(); BatchSuppressableTool unusedParametersInspection = profile.getUnwrappedTool(UnusedSymbolLocalInspectionBase.SHORT_NAME, parameter); @@ -703,9 +706,35 @@ public final class QuickFixFactoryImpl extends QuickFixFactory { SuppressQuickFix[] batchSuppressActions = unusedParametersInspection.getBatchSuppressActions(parameter); Collections.addAll(options, SuppressIntentionActionFromFix.convertBatchToSuppressIntentionActions(batchSuppressActions)); } - //need suppress from Unused Parameters but settings from Unused Symbol - QuickFixAction.registerQuickFixAction((HighlightInfo)highlightInfo, new SafeDeleteFix(parameter), - options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey)); + HighlightInfo info = (HighlightInfo)highlightInfo; + IntentionAction intentionAction; + if (excludingHierarchy) { + intentionAction = new AbstractIntentionAction() { + @Override + public @NotNull String getText() { + return JavaErrorBundle.message("parameter.excluding.hierarchy.disable.text"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + SetInspectionOptionFix.createFix(UnusedSymbolLocalInspectionBase.SHORT_NAME, + "myCheckParameterExcludingHierarchy", + JavaErrorBundle.message("parameter.excluding.hierarchy.disable.text"), false, + in -> { + return in instanceof UnusedDeclarationInspectionBase + ? ((UnusedDeclarationInspectionBase)in).getSharedLocalInspectionTool() + : in; + }) + .applyFix(project, file); + } + }; + } + else { + //need suppress from Unused Parameters but settings from Unused Symbol + intentionAction = new SafeDeleteFix(parameter); + } + info.registerFix(intentionAction, options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey), null, null); + QuickFixAction.registerQuickFixAction(info, createRenameToIgnoredFix(parameter, true), myUnusedSymbolKey); } @NotNull @@ -734,8 +763,8 @@ public final class QuickFixFactoryImpl extends QuickFixFactory { @NotNull @Override - public IntentionAction createRenameToIgnoredFix(@NotNull PsiNamedElement namedElement) { - return new RenameToIgnoredFix(namedElement); + public IntentionAction createRenameToIgnoredFix(@NotNull PsiNamedElement namedElement, boolean useElementNameAsSuffix) { + return RenameToIgnoredFix.createRenameToIgnoreFix(namedElement, useElementNameAsSuffix); } @NotNull diff --git a/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/OptionsPanel.form b/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/OptionsPanel.form index 13556ea0507c..509b981e3ae0 100644 --- a/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/OptionsPanel.form +++ b/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/OptionsPanel.form @@ -169,6 +169,15 @@ + + + + + + + + + diff --git a/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspection.java b/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspection.java index 1f9f5206ad7f..0e72ae39889f 100644 --- a/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/unusedSymbol/UnusedSymbolLocalInspection.java @@ -45,6 +45,7 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase private JLabel myFieldVisibilityCb; private JLabel myMethodVisibilityCb; private JLabel myMethodParameterVisibilityCb; + private JCheckBox myCheckParameterExcludingHierarchyCheckBox; private JCheckBox myInnerClassesCheckBox; private JLabel myInnerClassVisibilityCb; @@ -55,6 +56,7 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase myCheckMethodsCheckBox.setSelected(METHOD); myInnerClassesCheckBox.setSelected(INNER_CLASS); myCheckParametersCheckBox.setSelected(PARAMETER); + myCheckParameterExcludingHierarchyCheckBox.setSelected(myCheckParameterExcludingHierarchy); myAccessors.setSelected(!isIgnoreAccessors()); updateEnableState(); @@ -68,6 +70,7 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase METHOD = myCheckMethodsCheckBox.isSelected(); setIgnoreAccessors(!myAccessors.isSelected()); PARAMETER = myCheckParametersCheckBox.isSelected(); + setCheckParameterExcludingHierarchy(myCheckParameterExcludingHierarchyCheckBox.isSelected()); updateEnableState(); } @@ -77,6 +80,7 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase myCheckMethodsCheckBox.addActionListener(listener); myCheckClassesCheckBox.addActionListener(listener); myCheckParametersCheckBox.addActionListener(listener); + myCheckParameterExcludingHierarchyCheckBox.addActionListener(listener); myInnerClassesCheckBox.addActionListener(listener); myAccessors.addActionListener(listener); } @@ -87,6 +91,7 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase UIUtil.setEnabled(myFieldVisibilityCb, FIELD, true); UIUtil.setEnabled(myMethodVisibilityCb, METHOD, true); UIUtil.setEnabled(myMethodParameterVisibilityCb, PARAMETER, true); + setEnabledExcludingHierarchyCheckbox(getParameterVisibility()); myAccessors.setEnabled(METHOD); } @@ -114,7 +119,10 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase myMethodParameterVisibilityCb = new VisibilityModifierChooser(() -> PARAMETER, myParameterVisibility, - modifier -> setParameterVisibility(modifier)); + modifier -> { + setParameterVisibility(modifier); + setEnabledExcludingHierarchyCheckbox(modifier); + }); myAccessors = new JCheckBox() { @Override @@ -123,6 +131,10 @@ public class UnusedSymbolLocalInspection extends UnusedSymbolLocalInspectionBase } }; } + + private void setEnabledExcludingHierarchyCheckbox(@Nullable String modifier) { + myCheckParameterExcludingHierarchyCheckBox.setVisible(!PsiModifier.PRIVATE.equals(modifier)); + } } @Override diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java index 9e6b2b20e1b4..481938b84e60 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java @@ -49,7 +49,7 @@ public final class PsiUtil extends PsiUtilCore { public static final int ACCESS_LEVEL_PRIVATE = 1; public static final Key VALID_VOID_TYPE_IN_CODE_FRAGMENT = Key.create("VALID_VOID_TYPE_IN_CODE_FRAGMENT"); - private static final Pattern IGNORED_NAMES = Pattern.compile("ignored?\\d*"); + private static final Pattern IGNORED_NAMES = Pattern.compile("ignored?[A-Z]?[a-z]*\\d*"); private PsiUtil() {} diff --git a/java/java-psi-impl/src/messages/JavaErrorBundle.properties b/java/java-psi-impl/src/messages/JavaErrorBundle.properties index 1ca581c5baed..4ea88b25b12b 100644 --- a/java/java-psi-impl/src/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/src/messages/JavaErrorBundle.properties @@ -298,6 +298,7 @@ field.is.not.used=Field ''{0}'' is never used field.is.not.used.for.reading={0} field ''{1}'' is assigned but never accessed private.field.is.not.assigned=Private field ''{0}'' is never assigned parameter.is.not.used=Parameter ''{0}'' is never used +parameter.excluding.hierarchy.disable.text=Do not highlight parameters for inherited methods pattern.variable.is.not.used=Pattern variable ''{0}'' is never used private.method.is.not.used=Private method ''{0}'' is never used method.is.not.used=Method ''{0}'' is never used diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInAnonymousClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInAnonymousClass.java new file mode 100644 index 000000000000..7d052c826040 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInAnonymousClass.java @@ -0,0 +1,14 @@ +class Test { + public static void main(String[] args) { + I i2 = new I() { + @Override + public void foo(int p) { + } + }; + System.out.println(i2); + } +} + +interface I { + void foo(int i); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInLambda.java new file mode 100644 index 000000000000..a5c940d4638d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInLambda.java @@ -0,0 +1,10 @@ +class Test { + public static void main(String[] args) { + I i1 = p -> System.out.println(); + System.out.println(i1); + } +} + +interface I { + void foo(int i); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInOverriddenMethod.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInOverriddenMethod.java new file mode 100644 index 000000000000..dc631263b842 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInOverriddenMethod.java @@ -0,0 +1,9 @@ +interface I { + void foo(int i); +} + +class A implements I { + @Override + public void foo(int p) { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInPlainMethod.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInPlainMethod.java new file mode 100644 index 000000000000..fb5d2c0a1947 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/ExcludingHierarchyInPlainMethod.java @@ -0,0 +1,4 @@ +class Test { + void bar(int p) { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/IncludingHierarchy.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/IncludingHierarchy.java new file mode 100644 index 000000000000..ccf08d512865 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy/IncludingHierarchy.java @@ -0,0 +1,28 @@ +class Test { + public static void main(String[] args) { + I i1 = p -> System.out.println(); + System.out.println(i1); + I i2 = new I() { + @Override + public void foo(int p) { + } + }; + System.out.println(i2); + } + + static void foo(int i) { + } + + public void bar(int i) { + } +} + +interface I { + void foo(int i); +} + +class A implements I { + @Override + public void foo(int i) { + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/UnusedParameterInHierarchyTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/UnusedParameterInHierarchyTest.java new file mode 100644 index 000000000000..3dd50bd2448b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/UnusedParameterInHierarchyTest.java @@ -0,0 +1,68 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.java.codeInsight.daemon; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class UnusedParameterInHierarchyTest extends LightJavaCodeInsightFixtureTestCase { + private UnusedDeclarationInspection inspection; + + @Override + protected String getBasePath() { + return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/unusedParameterInHierarchy"; + } + + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return JAVA_16; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + inspection = new UnusedDeclarationInspection(); + myFixture.enableInspections(inspection); + } + + public void testExcludingHierarchyInAnonymousClass() { + inspection.getSharedLocalInspectionTool().setCheckParameterExcludingHierarchy(true); + doTest(); + assertNotNull(myFixture.getAvailableIntention("Rename 'p' to 'ignoredP'")); + assertNotNull(myFixture.getAvailableIntention("Do not highlight parameters for inherited methods")); + } + + public void testExcludingHierarchyInLambda() { + inspection.getSharedLocalInspectionTool().setCheckParameterExcludingHierarchy(true); + doTest(); + assertNotNull(myFixture.getAvailableIntention("Rename 'p' to 'ignoredP'")); + assertNotNull(myFixture.getAvailableIntention("Do not highlight parameters for inherited methods")); + } + + public void testExcludingHierarchyInOverriddenMethod() { + inspection.getSharedLocalInspectionTool().setCheckParameterExcludingHierarchy(true); + doTest(); + assertNotNull(myFixture.getAvailableIntention("Rename 'p' to 'ignoredP'")); + assertNotNull(myFixture.getAvailableIntention("Do not highlight parameters for inherited methods")); + } + + public void testExcludingHierarchyInPlainMethod() { + inspection.getSharedLocalInspectionTool().setCheckParameterExcludingHierarchy(true); + doTest(); + assertNotNull(myFixture.getAvailableIntention("Rename 'p' to 'ignoredP'")); + assertNull(myFixture.getAvailableIntention("Do not highlight parameters for inherited methods")); + } + + public void testIncludingHierarchy() { + inspection.getSharedLocalInspectionTool().setCheckParameterExcludingHierarchy(false); + doTest(); + } + + private void doTest() { + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.checkHighlighting(); + } +} diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index de3de234278d..133408a932e3 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -740,6 +740,7 @@ inspection.unused.symbol.check.inner.classes=Inner classes: inspection.unused.symbol.check.localvars=Local variables inspection.unused.symbol.check.methods=Methods: inspection.unused.symbol.check.parameters=Parameters in +inspection.unused.symbol.check.parameters.excluding.hierarchy=Excluding hierarchy inspection.value.based.warnings=Value-based warnings inspection.preview.feature=Preview Feature warning inspection.value.based.warnings.synchronization=Attempt to synchronize on an instance of a value-based class