/* * Copyright 2003-2013 Dave Griffith, Bas Leijdekkers * * 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.siyeh.ig.dataflow; import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.DefaultJDOMExternalizer; import com.intellij.openapi.util.WriteExternalException; import com.intellij.psi.*; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.psiutils.ParenthesesUtils; import com.siyeh.ig.psiutils.VariableAccessUtils; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.lang.reflect.Field; public class UnnecessaryLocalVariableInspectionBase extends BaseInspection { private static final String VARIABLES_NEW = "m_ignoreAnnotatedVariablesNew"; /** * @noinspection PublicField */ public boolean m_ignoreImmediatelyReturnedVariables = false; @Deprecated /** * @noinspection PublicField */ public boolean m_ignoreAnnotatedVariables = false; public boolean m_ignoreAnnotatedVariablesNew = true; @Override public void writeSettings(@NotNull Element node) throws WriteExternalException { DefaultJDOMExternalizer.writeExternal(this, node, new DefaultJDOMExternalizer.JDOMFilter() { @Override public boolean isAccept(@NotNull Field field) { return !Comparing.equal(VARIABLES_NEW, field.getName()); } }); if (!m_ignoreAnnotatedVariablesNew) { final Element option = new Element("option"); option.setAttribute("name", VARIABLES_NEW); option.setAttribute("value", Boolean.toString(m_ignoreAnnotatedVariablesNew)); node.addContent(option); } } @Override @NotNull public String getDisplayName() { return InspectionGadgetsBundle.message("redundant.local.variable.display.name"); } @Override public JComponent createOptionsPanel() { final MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this); optionsPanel.addCheckbox(InspectionGadgetsBundle.message("redundant.local.variable.ignore.option"), "m_ignoreImmediatelyReturnedVariables"); optionsPanel.addCheckbox(InspectionGadgetsBundle.message("redundant.local.variable.annotation.option"), "m_ignoreAnnotatedVariablesNew"); return optionsPanel; } @Override public boolean isEnabledByDefault() { return true; } @Override @NotNull public String buildErrorString(Object... infos) { return InspectionGadgetsBundle.message("unnecessary.local.variable.problem.descriptor"); } @Override public BaseInspectionVisitor buildVisitor() { return new UnnecessaryLocalVariableVisitor(); } private class UnnecessaryLocalVariableVisitor extends BaseInspectionVisitor { @SuppressWarnings({"IfStatementWithIdenticalBranches"}) @Override public void visitLocalVariable(@NotNull PsiLocalVariable variable) { super.visitLocalVariable(variable); if (m_ignoreAnnotatedVariablesNew) { final PsiModifierList list = variable.getModifierList(); if (list != null && list.getAnnotations().length > 0) { return; } } if (isCopyVariable(variable)) { registerVariableError(variable); } else if (!m_ignoreImmediatelyReturnedVariables && isImmediatelyReturned(variable)) { registerVariableError(variable); } else if (!m_ignoreImmediatelyReturnedVariables && isImmediatelyThrown(variable)) { registerVariableError(variable); } else if (isImmediatelyAssigned(variable)) { registerVariableError(variable); } else if (isImmediatelyAssignedAsDeclaration(variable)) { registerVariableError(variable); } } private boolean isCopyVariable(PsiVariable variable) { final PsiExpression initializer = ParenthesesUtils.stripParentheses(variable.getInitializer()); if (!(initializer instanceof PsiReferenceExpression)) { return false; } final PsiReferenceExpression reference = (PsiReferenceExpression)initializer; final PsiElement referent = reference.resolve(); if (referent == null) { return false; } if (!(referent instanceof PsiLocalVariable || referent instanceof PsiParameter)) { return false; } if (!(referent instanceof PsiResourceVariable) && variable instanceof PsiResourceVariable) { return false; } final PsiCodeBlock containingScope = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); if (containingScope == null) { return false; } if (!variable.hasModifierProperty(PsiModifier.FINAL) && VariableAccessUtils.variableIsAssigned(variable, containingScope, false)) { return false; } final PsiVariable initialization = (PsiVariable)referent; if (!initialization.hasModifierProperty(PsiModifier.FINAL) && VariableAccessUtils.variableIsAssigned(initialization, containingScope, false)) { return false; } if (!initialization.hasModifierProperty(PsiModifier.FINAL) && variable.hasModifierProperty(PsiModifier.FINAL) || PsiUtil.isLanguageLevel8OrHigher(initialization) && !HighlightControlFlowUtil.isEffectivelyFinal(initialization, containingScope, null) && HighlightControlFlowUtil.isEffectivelyFinal(variable, containingScope, null)) { for (PsiReference ref : ReferencesSearch.search(variable, new LocalSearchScope(containingScope))) { final PsiElement element = PsiTreeUtil.getParentOfType(ref.getElement(), PsiClass.class, PsiLambdaExpression.class); if (element != null && PsiTreeUtil.isAncestor(containingScope, element, true)) { return false; } } } return !TypeConversionUtil.boxingConversionApplicable(variable.getType(), initialization.getType()); } private boolean isImmediatelyReturned(PsiVariable variable) { final PsiCodeBlock containingScope = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class, true, PsiClass.class); if (containingScope == null) { return false; } final PsiElement parent = variable.getParent(); if (!(parent instanceof PsiDeclarationStatement)) { return false; } final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)parent; PsiStatement nextStatement = null; final PsiStatement[] statements = containingScope.getStatements(); for (int i = 0; i < (statements.length - 1); i++) { if (statements[i].equals(declarationStatement)) { nextStatement = statements[i + 1]; break; } } if (!(nextStatement instanceof PsiReturnStatement)) { return false; } final PsiReturnStatement returnStatement = (PsiReturnStatement)nextStatement; final PsiExpression returnValue = ParenthesesUtils.stripParentheses(returnStatement.getReturnValue()); if (!(returnValue instanceof PsiReferenceExpression)) { return false; } final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)returnValue; final PsiElement referent = referenceExpression.resolve(); if (referent == null || !referent.equals(variable)) { return false; } return !isVariableUsedInFollowingDeclarations(variable, declarationStatement); } private boolean isImmediatelyThrown(PsiVariable variable) { final PsiCodeBlock containingScope = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class, true, PsiClass.class); if (containingScope == null) { return false; } final PsiElement parent = variable.getParent(); if (!(parent instanceof PsiDeclarationStatement)) { return false; } final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)parent; PsiStatement nextStatement = null; final PsiStatement[] statements = containingScope.getStatements(); for (int i = 0; i < (statements.length - 1); i++) { if (statements[i].equals(declarationStatement)) { nextStatement = statements[i + 1]; break; } } if (!(nextStatement instanceof PsiThrowStatement)) { return false; } final PsiThrowStatement throwStatement = (PsiThrowStatement)nextStatement; final PsiExpression returnValue = ParenthesesUtils.stripParentheses(throwStatement.getException()); if (!(returnValue instanceof PsiReferenceExpression)) { return false; } final PsiElement referent = ((PsiReference)returnValue).resolve(); if (referent == null || !referent.equals(variable)) { return false; } return !isVariableUsedInFollowingDeclarations(variable, declarationStatement); } private boolean isImmediatelyAssigned(PsiVariable variable) { final PsiCodeBlock containingScope = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class, true, PsiClass.class); if (containingScope == null) { return false; } final PsiElement parent = variable.getParent(); if (!(parent instanceof PsiDeclarationStatement)) { return false; } final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)parent; PsiStatement nextStatement = null; int followingStatementNumber = 0; final PsiStatement[] statements = containingScope.getStatements(); for (int i = 0; i < (statements.length - 1); i++) { if (statements[i].equals(declarationStatement)) { nextStatement = statements[i + 1]; followingStatementNumber = i + 2; break; } } if (!(nextStatement instanceof PsiExpressionStatement)) { return false; } final PsiExpressionStatement expressionStatement = (PsiExpressionStatement)nextStatement; final PsiExpression expression = expressionStatement.getExpression(); if (!(expression instanceof PsiAssignmentExpression)) { return false; } final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression; final IElementType tokenType = assignmentExpression.getOperationTokenType(); if (tokenType != JavaTokenType.EQ) { return false; } final PsiExpression rhs = ParenthesesUtils.stripParentheses(assignmentExpression.getRExpression()); if (!(rhs instanceof PsiReferenceExpression)) { return false; } final PsiReferenceExpression reference = (PsiReferenceExpression)rhs; final PsiElement referent = reference.resolve(); if (referent == null || !referent.equals(variable)) { return false; } final PsiExpression lhs = assignmentExpression.getLExpression(); if (lhs instanceof PsiArrayAccessExpression) { return false; } if (isVariableUsedInFollowingDeclarations(variable, declarationStatement)) { return false; } for (int i = followingStatementNumber; i < statements.length; i++) { if (VariableAccessUtils.variableIsUsed(variable, statements[i])) { return false; } } return true; } private boolean isImmediatelyAssignedAsDeclaration(PsiVariable variable) { final PsiCodeBlock containingScope = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class, true, PsiClass.class); if (containingScope == null) { return false; } final PsiElement parent = variable.getParent(); if (!(parent instanceof PsiDeclarationStatement)) { return false; } final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)parent; PsiStatement nextStatement = null; int followingStatementNumber = 0; final PsiStatement[] statements = containingScope.getStatements(); for (int i = 0; i < (statements.length - 1); i++) { if (statements[i].equals(declarationStatement)) { nextStatement = statements[i + 1]; followingStatementNumber = i + 2; break; } } if (nextStatement instanceof PsiDeclarationStatement) { boolean referenceFound = false; final PsiDeclarationStatement nextDeclarationStatement = (PsiDeclarationStatement)nextStatement; for (PsiElement declaration : nextDeclarationStatement.getDeclaredElements()) { if (!(declaration instanceof PsiVariable)) { continue; } final PsiVariable nextVariable = (PsiVariable)declaration; final PsiExpression initializer = ParenthesesUtils.stripParentheses(nextVariable.getInitializer()); if (!referenceFound && initializer instanceof PsiReferenceExpression) { final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)initializer; final PsiElement referent = referenceExpression.resolve(); if (variable.equals(referent)) { referenceFound = true; continue; } } if (VariableAccessUtils.variableIsUsed(variable, initializer)) { return false; } } if (!referenceFound) { return false; } } else if (nextStatement instanceof PsiTryStatement) { final PsiTryStatement tryStatement = (PsiTryStatement)nextStatement; final PsiResourceList resourceList = tryStatement.getResourceList(); if (resourceList == null) { return false; } boolean referenceFound = false; for (PsiResourceVariable resourceVariable : resourceList.getResourceVariables()) { final PsiExpression initializer = resourceVariable.getInitializer(); if (!referenceFound && initializer instanceof PsiReferenceExpression) { final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)initializer; final PsiElement referent = referenceExpression.resolve(); if (variable.equals(referent)) { referenceFound = true; continue; } } if (VariableAccessUtils.variableIsUsed(variable, initializer)) { return false; } } if (!referenceFound) { return false; } if (VariableAccessUtils.variableIsUsed(variable, tryStatement.getTryBlock()) || VariableAccessUtils.variableIsUsed(variable, tryStatement.getFinallyBlock())) { return false; } for (PsiCatchSection section : tryStatement.getCatchSections()) { if (VariableAccessUtils.variableIsUsed(variable, section)) { return false; } } } else { return false; } if (isVariableUsedInFollowingDeclarations(variable, declarationStatement)) { return false; } for (int i = followingStatementNumber; i < statements.length; i++) { if (VariableAccessUtils.variableIsUsed(variable, statements[i])) { return false; } } return true; } private boolean isVariableUsedInFollowingDeclarations(PsiVariable variable, PsiDeclarationStatement declarationStatement) { final PsiElement[] declaredElements = declarationStatement.getDeclaredElements(); if (declaredElements.length == 1) { return false; } boolean check = false; for (PsiElement declaredElement : declaredElements) { if (!check && variable.equals(declaredElement)) { check = true; } else { if (VariableAccessUtils.variableIsUsed(variable, declaredElement)) { return true; } } } return false; } } }