blob: 0bc871664bfd1e5ba43cb6431afd0a554c25d7e4 [file] [log] [blame]
/*
* 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;
}
}
}