mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IG: add quick fixes and handle abstract classes better
in "Comparable implemented but 'equals()' not overridden" inspection
This commit is contained in:
+148
-38
@@ -15,34 +15,119 @@
|
||||
*/
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.javadoc.PsiDocToken;
|
||||
import com.intellij.psi.util.MethodSignatureUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.HardcodedMethodConstants;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.InspectionGadgetsFix;
|
||||
import com.siyeh.ig.psiutils.ClassUtils;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ComparableImplementedButEqualsNotOverriddenInspection extends BaseInspection {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"comparable.implemented.but.equals.not.overridden.display.name");
|
||||
return InspectionGadgetsBundle.message("comparable.implemented.but.equals.not.overridden.display.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"comparable.implemented.but.equals.not.overridden.problem.descriptor");
|
||||
return InspectionGadgetsBundle.message("comparable.implemented.but.equals.not.overridden.problem.descriptor");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected InspectionGadgetsFix[] buildFixes(Object... infos) {
|
||||
return new InspectionGadgetsFix[] {
|
||||
new GenerateEqualsMethodFix(),
|
||||
new AddNoteFix()
|
||||
};
|
||||
}
|
||||
|
||||
private static class GenerateEqualsMethodFix extends InspectionGadgetsFix {
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Generate 'equals()' method";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFix(Project project, ProblemDescriptor descriptor) {
|
||||
final PsiClass aClass = (PsiClass)descriptor.getPsiElement().getParent();
|
||||
final StringBuilder methodText = new StringBuilder();
|
||||
if (PsiUtil.isLanguageLevel5OrHigher(aClass)) {
|
||||
methodText.append("@java.lang.Override ");
|
||||
}
|
||||
methodText.append("public ");
|
||||
methodText.append("boolean equals(Object o) {\n");
|
||||
methodText.append("if (!(o instanceof ").append(aClass.getName()).append("))").append("return false;");
|
||||
methodText.append("return compareTo((").append(aClass.getName()).append(")o)==0;\n");
|
||||
methodText.append("}");
|
||||
final PsiMethod method =
|
||||
JavaPsiFacade.getElementFactory(project).createMethodFromText(methodText.toString(), aClass, PsiUtil.getLanguageLevel(aClass));
|
||||
final PsiElement newMethod = aClass.add(method);
|
||||
CodeStyleManager.getInstance(project).reformat(newMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddNoteFix extends InspectionGadgetsFix {
|
||||
|
||||
private static final Pattern PARAM_PATTERN = Pattern.compile("\\*[ \t]+@");
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Add 'ordering inconsistent with equals' JavaDoc note";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFix(Project project, ProblemDescriptor descriptor) {
|
||||
final PsiClass aClass = (PsiClass)descriptor.getPsiElement().getParent();
|
||||
final PsiDocComment comment = aClass.getDocComment();
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
if (comment == null) {
|
||||
final PsiDocComment newComment = factory.createDocCommentFromText(
|
||||
"/**\n" +
|
||||
"* Note: this class has a natural ordering that is inconsistent with equals.\n" +
|
||||
"*/", aClass);
|
||||
aClass.addBefore(newComment, aClass.getFirstChild());
|
||||
}
|
||||
else {
|
||||
final String text = comment.getText();
|
||||
final Matcher matcher = PARAM_PATTERN.matcher(text);
|
||||
String newCommentText;
|
||||
if (matcher.find()) {
|
||||
newCommentText = text.substring(0, matcher.start()) +
|
||||
" * Note: this class has a natural ordering that is inconsistent with equals.\n" +
|
||||
text.substring(matcher.start());
|
||||
}
|
||||
else {
|
||||
newCommentText = text.substring(0, text.length() - 2) +
|
||||
" * Note: this class has a natural ordering that is inconsistent with equals.\n*/";
|
||||
}
|
||||
final PsiDocComment newComment = factory.createDocCommentFromText(newCommentText);
|
||||
comment.replace(newComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,43 +141,68 @@ public class ComparableImplementedButEqualsNotOverriddenInspection extends BaseI
|
||||
public void visitClass(PsiClass aClass) {
|
||||
super.visitClass(aClass);
|
||||
if (aClass.isInterface()) {
|
||||
// the problem can't be fixed for an interface, so let's not report it
|
||||
return;
|
||||
}
|
||||
final PsiMethod[] methods = aClass.findMethodsByName(HardcodedMethodConstants.COMPARE_TO, false);
|
||||
if (methods.length == 0) {
|
||||
return;
|
||||
}
|
||||
final Project project = aClass.getProject();
|
||||
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
|
||||
final GlobalSearchScope scope = aClass.getResolveScope();
|
||||
final PsiClass comparableClass =
|
||||
psiFacade.findClass(CommonClassNames.JAVA_LANG_COMPARABLE,
|
||||
scope);
|
||||
if (comparableClass == null) {
|
||||
JavaPsiFacade.getInstance(aClass.getProject()).findClass(CommonClassNames.JAVA_LANG_COMPARABLE, aClass.getResolveScope());
|
||||
if (comparableClass == null || !aClass.isInheritor(comparableClass, true)) {
|
||||
return;
|
||||
}
|
||||
if (!aClass.isInheritor(comparableClass, true)) {
|
||||
final PsiMethod[] comparableMethods = comparableClass.getMethods();
|
||||
if (comparableMethods.length != 1) { // incorrect/broken jdk
|
||||
return;
|
||||
}
|
||||
final PsiMethod compareToMethod = comparableClass.getMethods()[0];
|
||||
boolean foundCompareTo = false;
|
||||
for (PsiMethod method : methods) {
|
||||
if (MethodSignatureUtil.isSuperMethod(compareToMethod, method)) {
|
||||
foundCompareTo = true;
|
||||
break;
|
||||
final PsiMethod comparableMethod = MethodSignatureUtil.findMethodBySuperMethod(aClass, comparableMethods[0], false);
|
||||
if (comparableMethod == null || comparableMethod.hasModifierProperty(PsiModifier.ABSTRACT) ||
|
||||
comparableMethod.getBody() == null) {
|
||||
return;
|
||||
}
|
||||
final PsiClass objectClass = ClassUtils.findObjectClass(aClass);
|
||||
if (objectClass == null) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod[] equalsMethods = objectClass.findMethodsByName(HardcodedMethodConstants.EQUALS, false);
|
||||
if (equalsMethods.length != 1) { // incorrect/broken jdk
|
||||
return;
|
||||
}
|
||||
final PsiMethod equalsMethod = MethodSignatureUtil.findMethodBySuperMethod(aClass, equalsMethods[0], false);
|
||||
if (equalsMethod != null && !equalsMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
return;
|
||||
}
|
||||
final String docCommentText = collapseWhitespace(getActualCommentText(aClass.getDocComment()));
|
||||
if (StringUtil.containsIgnoreCase(docCommentText, "this class has a natural ordering that is inconsistent with equals")) {
|
||||
// see Comparable.compareTo() javadoc
|
||||
return;
|
||||
}
|
||||
registerClassError(aClass, aClass);
|
||||
}
|
||||
|
||||
private static String getActualCommentText(PsiDocComment comment) {
|
||||
if (comment == null) return "";
|
||||
return Arrays.stream(comment.getChildren())
|
||||
.filter(e -> (e instanceof PsiDocToken) && ((PsiDocToken)e).getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA)
|
||||
.map(PsiElement::getText)
|
||||
.collect(Collectors.joining());
|
||||
}
|
||||
|
||||
private static String collapseWhitespace(String s) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
boolean space = false;
|
||||
for (int i = 0, length = s.length(); i < length; i++) {
|
||||
char ch = s.charAt(i);
|
||||
if (StringUtil.isWhiteSpace(ch)) {
|
||||
if (!space) {
|
||||
result.append(' ');
|
||||
space = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.append(ch);
|
||||
space = false;
|
||||
}
|
||||
}
|
||||
if (!foundCompareTo) {
|
||||
return;
|
||||
}
|
||||
final PsiMethod[] equalsMethods = aClass.findMethodsByName(
|
||||
HardcodedMethodConstants.EQUALS, false);
|
||||
for (PsiMethod equalsMethod : equalsMethods) {
|
||||
if (MethodUtils.isEquals(equalsMethod)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
registerClassError(aClass);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
abstract class <warning descr="Class 'AbstractClass1' implements 'java.lang.Comparable' but does not override 'equals()'">AbstractClass1</warning> implements Comparable<AbstractClass1> {
|
||||
|
||||
int field = 1;
|
||||
|
||||
public int compareTo(AbstractClass1 other) {
|
||||
return field > other.field ? 1 : (field == other.field ? 0 : -1);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
abstract class AbstractClass2 implements Comparable<AbstractClass2> {
|
||||
|
||||
public abstract int compareTo(AbstractClass2 other);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
abstract class <warning descr="Class 'AbstractClass3' implements 'java.lang.Comparable' but does not override 'equals()'">AbstractClass3</warning> implements Comparable<AbstractClass3> {
|
||||
|
||||
int field;
|
||||
|
||||
public int compareTo(AbstractClass3 other) {
|
||||
return field > other.field ? 1 : (field == other.field ? 0 : -1);
|
||||
}
|
||||
|
||||
public abstract boolean equals(Object other);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Note: This class has a natural
|
||||
* ordering that is INCONSISTENT with equals.
|
||||
*/
|
||||
class Note implements Comparable<Note> {
|
||||
|
||||
@Override
|
||||
public int compareTo(Note other) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+4
@@ -26,6 +26,10 @@ public class ComparableImplementedButEqualsNotOverriddenInspectionTest extends L
|
||||
|
||||
public void testInterfaceImplementingComparable() { doTest(); }
|
||||
public void testSimple() { doTest(); }
|
||||
public void testAbstractClass1() { doTest(); }
|
||||
public void testAbstractClass2() { doTest(); }
|
||||
public void testAbstractClass3() { doTest(); }
|
||||
public void testNote() { doTest(); }
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user