mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-27 16:57:49 +08:00
200 lines
7.5 KiB
Java
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();
|
|
}
|
|
}
|
|
|
|
}
|