JohnHake 0639ae696e Rewrite comparing references inspection
Rewrite inspections tutorial doc
2019-04-07 18:46:39 -07:00

200 lines
7.5 KiB
Java

package com.intellij.codeInspection;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.ui.DocumentAdapter;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.util.StringTokenizer;
import static com.siyeh.ig.psiutils.ExpressionUtils.isNullLiteral;
/**
* @author max
* @author jhake
*/
public class ComparingReferencesInspection extends AbstractBaseJavaLocalInspectionTool {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ComparingReferencesInspection");
private final CriQuickFix myQuickFix = new CriQuickFix();
// Defines the text of the quick fix intention
public static final String QUICK_FIX_NAME = "SDK: " + InspectionsBundle.message("inspection.comparing.references.use.quickfix");
// This string holds a list of classes relevant to this inspection.
@SuppressWarnings({"WeakerAccess"})
@NonNls
public String CHECKED_CLASSES = "java.lang.String;java.util.Date";
/**
* This method is called to get the panel describing the inspection.
* It is called every time the user selects the inspection in preferences.
* The user has the option to edit the list of CHECKED_CLASSES.
* Adds a document listener to see if
*
* @return panel to display inspection information.
*/
@Override
public JComponent createOptionsPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
final JTextField checkedClasses = new JTextField(CHECKED_CLASSES);
checkedClasses.getDocument().addDocumentListener(new DocumentAdapter() {
public void textChanged(DocumentEvent event) {
CHECKED_CLASSES = checkedClasses.getText();
}
});
panel.add(checkedClasses);
return panel;
}
/**
* This method is overridden to provide a custom visitor
* that inspects expressions with relational operators '==' and '!='
* The visitor must not be recursive and must be thread-safe.
*
* @param holder object for visitor to register problems found.
* @param isOnTheFly true if inspection was run in non-batch mode
* @return non-null visitor for this inspection.
* @see JavaElementVisitor
*/
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
/**
* This string defines the short message shown to a user signaling the inspection
* found a problem. It reuses a string from the inspections bundle.
*/
@NonNls
private final String DESCRIPTION_TEMPLATE = "SDK " + InspectionsBundle.message("inspection.comparing.references.problem.descriptor");
/**
* Avoid defining visitors for both Reference and Binary expressions.
*
* @param psiReferenceExpression The expression to be evaluated.
*/
@Override
public void visitReferenceExpression(PsiReferenceExpression psiReferenceExpression) {
}
/**
* Evaluate binary psi expressions to see if they contain
* relational operators '==' and '!=', AND they contain
* classes contained in CHECKED_CLASSES. The evaluation
* ignores expressions comparing an object to null.
* IF this criteria is met, add the expression to the
* problems list.
*
* @param expression The binary expression to be evaluated.
*/
@Override
public void visitBinaryExpression(PsiBinaryExpression expression) {
super.visitBinaryExpression(expression);
IElementType opSign = expression.getOperationTokenType();
if (opSign == JavaTokenType.EQEQ || opSign == JavaTokenType.NE) {
// The binary expression is the correct type for this inspection
PsiExpression lOperand = expression.getLOperand();
PsiExpression rOperand = expression.getROperand();
if (rOperand == null || isNullLiteral(lOperand) || isNullLiteral(rOperand))
return;
// Nothing is compared to null, now check the types being compared
PsiType lType = lOperand.getType();
PsiType rType = rOperand.getType();
if (isCheckedType(lType) || isCheckedType(rType)) {
// Identified an expression with potential problems, add to list with fix object.
holder.registerProblem(expression,
DESCRIPTION_TEMPLATE, myQuickFix);
}
}
}
/**
* Verifies the input is the correct {@code PsiType} for this inspection.
*
* @param type The {@code PsiType} to be examined for a match
* @return {@code true} if input is {@code PsiClassType} and matches
* one of the classes in the CHECKED_CLASSES list.
*/
private boolean isCheckedType(PsiType type) {
if (!(type instanceof PsiClassType))
return false;
StringTokenizer tokenizer = new StringTokenizer(CHECKED_CLASSES, ";");
while (tokenizer.hasMoreTokens()) {
String className = tokenizer.nextToken();
if (type.equalsToText(className))
return true;
}
return false;
}
};
}
/**
* This class provides a solution to inspection problem expressions by manipulating
* the PSI tree to use a.equals(b) instead of '==' or '!='
*/
private static class CriQuickFix implements LocalQuickFix {
/**
* Returns a partially localized string for the quick fix intention.
* Used by the test code for this plugin.
*
* @return Quick fix short name.
*/
@NotNull
@Override
public String getName() {
return QUICK_FIX_NAME;
}
/**
* This method manipulates the PSI tree to replace 'a==b' with 'a.equals(b)
* or 'a!=b' with '!a.equals(b)'
*
* @param project The project that contains the file being edited.
* @param descriptor A problem found by this inspection.
*/
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
try {
PsiBinaryExpression binaryExpression = (PsiBinaryExpression) descriptor.getPsiElement();
IElementType opSign = binaryExpression.getOperationTokenType();
PsiExpression lExpr = binaryExpression.getLOperand();
PsiExpression rExpr = binaryExpression.getROperand();
if (rExpr == null)
return;
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiMethodCallExpression equalsCall =
(PsiMethodCallExpression) factory.createExpressionFromText("a.equals(b)", null);
equalsCall.getMethodExpression().getQualifierExpression().replace(lExpr);
equalsCall.getArgumentList().getExpressions()[0].replace(rExpr);
PsiExpression result = (PsiExpression) binaryExpression.replace(equalsCall);
if (opSign == JavaTokenType.NE) {
PsiPrefixExpression negation = (PsiPrefixExpression) factory.createExpressionFromText("!a", null);
negation.getOperand().replace(result);
result.replace(negation);
}
} catch (IncorrectOperationException e) {
LOG.error(e);
}
}
@NotNull
public String getFamilyName() {
return getName();
}
}
}