unused declaration: move unused local variables check to unused declaration

This commit is contained in:
Anna Kozlova
2016-09-05 07:57:43 +03:00
parent 874d6d8d5b
commit c48703ed91
13 changed files with 254 additions and 109 deletions
@@ -107,7 +107,7 @@ public class UnusedDeclarationInspectionBase extends GlobalInspectionTool {
return new UnusedSymbolLocalInspectionBase();
}
@Nullable
@NotNull
@Override
public UnusedSymbolLocalInspectionBase getSharedLocalInspectionTool() {
return myLocalInspectionBase;
@@ -88,24 +88,15 @@ public class DefUseInspectionBase extends BaseJavaBatchLocalInspectionTool {
PsiVariable psiVariable = info.getVariable();
if (context instanceof PsiDeclarationStatement || context instanceof PsiResourceVariable) {
if (!info.isRead()) {
if (!isOnTheFly) {
holder.registerProblem(ObjectUtils.notNull(psiVariable.getNameIdentifier(), psiVariable),
InspectionsBundle.message("inspection.unused.assignment.problem.descriptor1", "<code>#ref</code> #loc"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
else {
if (REPORT_REDUNDANT_INITIALIZER) {
List<LocalQuickFix> fixes = ContainerUtil.createMaybeSingletonList(
isOnTheFlyOrNoSideEffects(isOnTheFly, psiVariable, psiVariable.getInitializer()) ? createRemoveInitializerFix() : null);
holder.registerProblem(ObjectUtils.notNull(psiVariable.getInitializer(), psiVariable),
InspectionsBundle.message("inspection.unused.assignment.problem.descriptor2",
"<code>" + psiVariable.getName() + "</code>", "<code>#ref</code> #loc"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
fixes.toArray(new LocalQuickFix[fixes.size()])
);
}
if (info.isRead() && REPORT_REDUNDANT_INITIALIZER) {
List<LocalQuickFix> fixes = ContainerUtil.createMaybeSingletonList(
isOnTheFlyOrNoSideEffects(isOnTheFly, psiVariable, psiVariable.getInitializer()) ? createRemoveInitializerFix() : null);
holder.registerProblem(ObjectUtils.notNull(psiVariable.getInitializer(), psiVariable),
InspectionsBundle.message("inspection.unused.assignment.problem.descriptor2",
"<code>" + psiVariable.getName() + "</code>", "<code>#ref</code> #loc"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
fixes.toArray(new LocalQuickFix[fixes.size()])
);
}
}
else if (context instanceof PsiAssignmentExpression) {
@@ -127,22 +118,6 @@ public class DefUseInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
}
}
if (!isOnTheFly) {
body.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(PsiClass aClass) { }
@Override
public void visitLocalVariable(PsiLocalVariable variable) {
if (!usedVariables.contains(variable) && variable.getInitializer() == null) {
PsiElement element = ObjectUtils.notNull(variable.getNameIdentifier(), variable);
String message = InspectionsBundle.message("inspection.unused.assignment.problem.descriptor5", "<code>#ref</code> #loc");
holder.registerProblem(element, message, ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
});
}
}
private static boolean isOnTheFlyOrNoSideEffects(boolean isOnTheFly,
@@ -18,18 +18,23 @@ package com.intellij.codeInspection.deadCode;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.EntryPointsManagerImpl;
import com.intellij.codeInspection.reference.EntryPoint;
import com.intellij.codeInspection.reference.RefEntity;
import com.intellij.codeInspection.reference.RefMethod;
import com.intellij.codeInspection.reference.RefVisitor;
import com.intellij.codeInspection.ex.GlobalInspectionContextImpl;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
import com.intellij.codeInspection.ex.Tools;
import com.intellij.codeInspection.reference.*;
import com.intellij.codeInspection.ui.InspectionToolPresentation;
import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection;
import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.JBUI;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
@@ -38,6 +43,8 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.List;
public class UnusedDeclarationInspection extends UnusedDeclarationInspectionBase {
private final UnusedParametersInspection myUnusedParameters = new UnusedParametersInspection();
@@ -78,6 +85,12 @@ public class UnusedDeclarationInspection extends UnusedDeclarationInspectionBase
super.runInspection(scope, manager, globalContext, problemDescriptionsProcessor);
}
@Nullable
@Override
public RefGraphAnnotator getAnnotator(@NotNull RefManager refManager) {
return new UnusedVariablesGraphAnnotator(InspectionManager.getInstance(refManager.getProject()), refManager);
}
@Override
public boolean queryExternalUsagesRequests(@NotNull InspectionManager manager,
@NotNull GlobalInspectionContext globalContext,
@@ -218,4 +231,91 @@ public class UnusedDeclarationInspection extends UnusedDeclarationInspectionBase
}
}
private class UnusedVariablesGraphAnnotator extends RefGraphAnnotator {
private final InspectionManager myInspectionManager;
private GlobalInspectionContextImpl myContext;
private Map<String, Tools> myTools;
public UnusedVariablesGraphAnnotator(InspectionManager inspectionManager, RefManager refManager) {
myInspectionManager = inspectionManager;
myContext = (GlobalInspectionContextImpl)((RefManagerImpl)refManager).getContext();
myTools = myContext.getTools();
}
@Override
public void onReferencesBuild(RefElement refElement) {
if (refElement instanceof RefClass) {
PsiClass aClass = ((RefClass)refElement).getElement();
if (aClass != null) {
for (PsiClassInitializer initializer : aClass.getInitializers()) {
findUnusedVariables(initializer.getBody(), refElement, aClass);
}
}
}
else if (refElement instanceof RefMethod) {
PsiElement element = refElement.getElement();
if (element instanceof PsiMethod) {
PsiCodeBlock body = ((PsiMethod)element).getBody();
if (body != null) {
findUnusedVariables(body, refElement, element);
}
}
}
}
private void findUnusedVariables(PsiCodeBlock body, RefElement refElement, PsiElement element) {
Tools tools = myTools.get(getShortName());
if (tools.isEnabled(element)) {
InspectionToolWrapper toolWrapper = tools.getInspectionTool(element);
InspectionToolPresentation presentation = myContext.getPresentation(toolWrapper);
if (((UnusedDeclarationInspection)toolWrapper.getTool()).getSharedLocalInspectionTool().LOCAL_VARIABLE) {
List<CommonProblemDescriptor> descriptors = new ArrayList<>();
final Set<PsiVariable> usedVariables = new THashSet<>();
List<DefUseUtil.Info> unusedDefs = DefUseUtil.getUnusedDefs(body, usedVariables);
if (unusedDefs != null && !unusedDefs.isEmpty()) {
for (DefUseUtil.Info info : unusedDefs) {
PsiElement parent = info.getContext();
PsiVariable psiVariable = info.getVariable();
if (parent instanceof PsiDeclarationStatement || parent instanceof PsiResourceVariable) {
if (!info.isRead()) {
descriptors.add(createProblemDescriptor(psiVariable));
}
}
}
}
body.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(PsiClass aClass) { }
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {} //todo
@Override
public void visitLocalVariable(PsiLocalVariable variable) {
if (!usedVariables.contains(variable) && variable.getInitializer() == null) {
descriptors.add(createProblemDescriptor(variable));
}
}
});
if (!descriptors.isEmpty()) {
presentation.addProblemElement(refElement, descriptors.toArray(CommonProblemDescriptor.EMPTY_ARRAY));
}
}
}
}
private ProblemDescriptor createProblemDescriptor(PsiVariable psiVariable) {
PsiElement toHighlight = ObjectUtils.notNull(psiVariable.getNameIdentifier(), psiVariable);
return myInspectionManager.createProblemDescriptor(
toHighlight,
InspectionsBundle.message("inspection.unused.assignment.problem.descriptor1", "<code>#ref</code> #loc"), (LocalQuickFix)null,
ProblemHighlightType.LIKE_UNUSED_SYMBOL, false);
}
}
}
@@ -188,6 +188,7 @@ public class UnusedDeclarationPresentation extends DefaultInspectionToolPresenta
descriptionElement.addContent(buf.toString());
element.addContent(descriptionElement);
}
super.exportResults(parentNode, refEntity, excludedDescriptions);
}
@Override
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
</problems>
@@ -0,0 +1,6 @@
class A {
public static void main(String[] args) {
int i = 0;
}
}
@@ -22,7 +22,7 @@ import com.intellij.testFramework.InspectionTestCase;
public abstract class AbstractUnusedDeclarationTest extends InspectionTestCase {
protected UnusedDeclarationInspection myTool;
private GlobalInspectionToolWrapper myToolWrapper;
protected GlobalInspectionToolWrapper myToolWrapper;
@Override
protected String getTestDataPath() {
@@ -37,6 +37,8 @@ public abstract class AbstractUnusedDeclarationTest extends InspectionTestCase {
}
protected void doTest() {
myTool.getSharedLocalInspectionTool().LOCAL_VARIABLE = false;
myTool.getSharedLocalInspectionTool().PARAMETER = false;
doTest("deadCode/" + getTestName(true), myToolWrapper);
}
}
@@ -22,8 +22,8 @@ package com.intellij.codeInspection;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInspection.actions.ViewOfflineResultsAction;
import com.intellij.codeInspection.defUse.DefUseInspection;
import com.intellij.codeInspection.defUse.DefUseInspectionBase;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
import com.intellij.codeInspection.ex.*;
import com.intellij.codeInspection.offline.OfflineProblemDescriptor;
import com.intellij.codeInspection.offlineViewer.OfflineProblemDescriptorNode;
@@ -54,26 +54,26 @@ import java.util.Set;
public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
private InspectionResultsView myView;
private LocalInspectionToolWrapper myUnusedToolWrapper;
private GlobalInspectionToolWrapper myUnusedToolWrapper;
private LocalInspectionToolWrapper myDataFlowToolWrapper;
private static String varMessage(String name) {
return InspectionsBundle.message("inspection.unused.assignment.problem.descriptor1", "'" + name + "'");
private static String varMessage() {
return "Problem detected by global inspection 'Unused declaration'";
}
@Override
protected void setUp() throws Exception {
super.setUp();
HighlightDisplayKey key = HighlightDisplayKey.find(DefUseInspectionBase.SHORT_NAME);
HighlightDisplayKey key = HighlightDisplayKey.find(UnusedDeclarationInspectionBase.SHORT_NAME);
if (key == null) {
HighlightDisplayKey.register(DefUseInspectionBase.SHORT_NAME);
HighlightDisplayKey.register(UnusedDeclarationInspectionBase.SHORT_NAME);
}
final InspectionProfileImpl profile = new InspectionProfileImpl("test") {
@Override
public boolean isToolEnabled(@Nullable final HighlightDisplayKey key, PsiElement element) {
return key != null && Comparing.strEqual(key.toString(), DefUseInspectionBase.SHORT_NAME);
return key != null && Comparing.strEqual(key.toString(), UnusedDeclarationInspectionBase.SHORT_NAME);
}
@Override
@@ -94,18 +94,18 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
@Override
public boolean isToolEnabled(@Nullable HighlightDisplayKey key, PsiElement element) {
return key != null && Comparing.strEqual(key.toString(), DefUseInspectionBase.SHORT_NAME);
return key != null && Comparing.strEqual(key.toString(), UnusedDeclarationInspectionBase.SHORT_NAME);
}
};
}
};
myView = ViewOfflineResultsAction.showOfflineView(getProject(), parse(), profile, "");
myUnusedToolWrapper = new LocalInspectionToolWrapper(new DefUseInspection());
myUnusedToolWrapper = new GlobalInspectionToolWrapper(new UnusedDeclarationInspection());
myDataFlowToolWrapper = new LocalInspectionToolWrapper(new EqualsWithItselfInspection());
final Map<String, Tools> tools = myView.getGlobalInspectionContext().getTools();
for (LocalInspectionToolWrapper tool : ContainerUtil.ar(myUnusedToolWrapper, myDataFlowToolWrapper)) {
for (InspectionToolWrapper tool : ContainerUtil.ar(myUnusedToolWrapper, myDataFlowToolWrapper)) {
profile.addTool(getProject(), tool, new THashMap<>());
tools.put(tool.getShortName(), new ToolsImpl(tool, tool.getDefaultLevel(), true));
tool.initialize(myView.getGlobalInspectionContext());
@@ -151,6 +151,25 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
InspectionTree tree = updateTree();
TreeUtil.expandAll(tree);
PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n" +
" -Declaration redundancy\n" +
" -" + myUnusedToolWrapper + "\n"
+ " -" + getModule().toString() + "\n"
+ " -<default>\n"
+ " -Test\n"
+ " -foo()\n"
+ " " + varMessage() + "\n"
+ " -main(String[])\n"
+ " " + varMessage() + "\n"
+ " -f()\n"
+ " -D\n"
+ " -b()\n"
+ " " + varMessage() + "\n"
+ " -anonymous (java.lang.Runnable)\n"
+ " -run()\n"
+ " " + varMessage() + "\n"
+ " -ff()\n"
+ " " + varMessage() + "\n"
+ " " + varMessage() + "\n" +
" -Probable bugs\n" +
" -" + myDataFlowToolWrapper + "\n" +
" -Module: 'testOfflineWithInvalid'\n" +
@@ -160,25 +179,8 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
" 'equals()' called on itself\n" +
" -null\n" +
" Identical qualifier and argument to <code>equals()</code> call\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -" + getModule().toString() + "\n"
+ " -<default>\n"
+ " -Test\n"
+ " -foo()\n"
+ " " + varMessage("j") + "\n"
+ " -main(String[])\n"
+ " " + varMessage("test") + "\n"
+ " -f()\n"
+ " -D\n"
+ " -b()\n"
+ " " + varMessage("r") + "\n"
+ " -anonymous (java.lang.Runnable)\n"
+ " -run()\n"
+ " " + varMessage("i") + "\n"
+ " -ff()\n"
+ " " + varMessage("d") + "\n"
+ " " + varMessage("a") + "\n");
tree.setSelectionRow(8);
);
tree.setSelectionRow(27);
final OfflineProblemDescriptorNode node =
(OfflineProblemDescriptorNode)tree.getSelectionModel().getSelectionPath().getLastPathComponent();
assertFalse(node.isValid());
@@ -189,6 +191,25 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
InspectionTree tree = updateTree();
TreeUtil.expandAll(tree);
PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n" +
" -Declaration redundancy\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -" + getModule().toString() + "\n"
+ " -<default>\n"
+ " -Test\n"
+ " -foo()\n"
+ " " + varMessage() + "\n"
+ " -main(String[])\n"
+ " " + varMessage() + "\n"
+ " -f()\n"
+ " -D\n"
+ " -b()\n"
+ " " + varMessage() + "\n"
+ " -anonymous (java.lang.Runnable)\n"
+ " -run()\n"
+ " " + varMessage() + "\n"
+ " -ff()\n"
+ " " + varMessage() + "\n"
+ " " + varMessage() + "\n" +
" -Probable bugs\n" +
" -" + myDataFlowToolWrapper + "\n" +
" -Module: 'testOfflineView'\n" +
@@ -199,43 +220,36 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
" -Test2\n" +
" -m123()\n" +
" 'equals()' called on itself\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -" + getModule().toString() + "\n"
+ " -<default>\n"
+ " -Test\n"
+ " -foo()\n"
+ " " + varMessage("j") + "\n"
+ " -main(String[])\n"
+ " " + varMessage("test") + "\n"
+ " -f()\n"
+ " -D\n"
+ " -b()\n"
+ " " + varMessage("r") + "\n"
+ " -anonymous (java.lang.Runnable)\n"
+ " -run()\n"
+ " " + varMessage("i") + "\n"
+ " -ff()\n"
+ " " + varMessage("d") + "\n"
+ " " + varMessage("a") + "\n");
myView.getGlobalInspectionContext().getUIOptions().SHOW_STRUCTURE = false;
);
myView.getGlobalInspectionContext().getUIOptions().SHOW_STRUCTURE = false;
tree = updateTree();
PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n"
+ " -Declaration redundancy\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -Test\n"
+ " -foo()\n"
+ " " + varMessage() + "\n"
+ " -main(String[])\n"
+ " " + varMessage() + "\n"
+ " -f()\n"
+ " -D\n"
+ " -b()\n"
+ " " + varMessage() + "\n"
+ " -anonymous (java.lang.Runnable)\n"
+ " -run()\n"
+ " " + varMessage() + "\n"
+ " -ff()\n"
+ " " + varMessage() + "\n"
+ " " + varMessage() + "\n"
+ " -Probable bugs\n"
+ " -" + myDataFlowToolWrapper + "\n" +
" -Test\n" +
" 'equals()' called on itself\n" +
" -Test2\n" +
" 'equals()' called on itself\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -Test\n"
+ " " + varMessage("j") + "\n"
+ " " + varMessage("test") + "\n"
+ " " + varMessage("r") + "\n"
+ " " + varMessage("i") + "\n"
+ " " + varMessage("d") + "\n"
+ " " + varMessage("a") + "\n");
TreeUtil.selectFirstNode(tree);
final InspectionTreeNode root = (InspectionTreeNode)tree.getLastSelectedPathComponent();
);
TreeUtil.selectNode(tree, tree.getRoot());
final InspectionTreeNode root = tree.getRoot();
root.excludeElement(myView.getExcludedManager());
TreeUtil.traverse(root, new TreeUtil.Traverse() {
@Override
@@ -250,20 +264,30 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase {
myView.getGlobalInspectionContext().getUIOptions().FILTER_RESOLVED_ITEMS = false;
tree = updateTree();
PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n"
+ " -Declaration redundancy\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -Test\n"
+ " -foo()\n"
+ " " + varMessage() + "\n"
+ " -main(String[])\n"
+ " " + varMessage() + "\n"
+ " -f()\n"
+ " -D\n"
+ " -b()\n"
+ " " + varMessage() + "\n"
+ " -anonymous (java.lang.Runnable)\n"
+ " -run()\n"
+ " " + varMessage() + "\n"
+ " -ff()\n"
+ " " + varMessage() + "\n"
+ " " + varMessage() + "\n"
+ " -Probable bugs\n"
+ " -" + myDataFlowToolWrapper + "\n" +
" -Test\n" +
" 'equals()' called on itself\n" +
" -Test2\n" +
" 'equals()' called on itself\n"
+ " -" + myUnusedToolWrapper + "\n"
+ " -Test\n"
+ " " + varMessage("j") + "\n"
+ " " + varMessage("test") + "\n"
+ " " + varMessage("r") + "\n"
+ " " + varMessage("i") + "\n"
+ " " + varMessage("d") + "\n"
+ " " + varMessage("a") + "\n");
);
}
private InspectionTree updateTree() {
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection;
public class UnusedLocalDeclarationTest extends AbstractUnusedDeclarationTest {
@Override
protected void doTest() {
myTool.getSharedLocalInspectionTool().PARAMETER = false;
doTest("deadCode/" + getTestName(true), myToolWrapper);
}
public void testLocalVariables() {
doTest();
}
}
@@ -21,7 +21,9 @@ public class UnusedMethodParameterTest extends InspectionTestCase {
}
private void doTest() throws Exception {
doTest("unusedMethodParameter/" + getTestName(true), new UnusedDeclarationInspection());
UnusedDeclarationInspection declarationInspection = new UnusedDeclarationInspection();
declarationInspection.getSharedLocalInspectionTool().LOCAL_VARIABLE = false;
doTest("unusedMethodParameter/" + getTestName(true), declarationInspection);
}
public void testFieldInAnonymousClass() throws Exception {
@@ -66,6 +66,7 @@ public abstract class InspectionTestCase extends PsiTestCase {
ep.presentation = UnusedDeclarationPresentation.class.getName();
ep.implementationClass = UnusedDeclarationInspection.class.getName();
ep.shortName = UnusedDeclarationInspectionBase.SHORT_NAME;
ep.displayName = UnusedDeclarationInspectionBase.DISPLAY_NAME;
return new GlobalInspectionToolWrapper(ep);
}
@@ -317,7 +317,7 @@ inspection.javadoc.html.not.required.label.text=Additional Not Required Html Att
inspection.javadoc.html.not.required.dialog.title=Edit Additional Not Required Html Attributes
inspection.required.attributes.display.name=Missing required attribute
inspection.unused.symbol.check.localvars=Local variables (Editor only)
inspection.unused.symbol.check.localvars=Local variables
inspection.unused.symbol.check.fields=Fields:
inspection.unused.symbol.check.methods=Methods:
inspection.unused.symbol.check.accessors=Getters/setters