mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
implement SuperClassHasFrequentlyUsedInheritors inspection over compiler indices
This commit is contained in:
+25
-8
@@ -62,7 +62,7 @@ class CompilerReferenceReader {
|
||||
}
|
||||
else {
|
||||
LightRef.LightClassHierarchyElementDef hierarchyElement = ((LightRef.LightMember)ref).getOwner();
|
||||
hierarchy = getWholeHierarchy(hierarchyElement, checkBaseClassAmbiguity, -1);
|
||||
hierarchy = getHierarchy(hierarchyElement, checkBaseClassAmbiguity, false, -1);
|
||||
}
|
||||
if (hierarchy == null) return null;
|
||||
TIntHashSet set = new TIntHashSet();
|
||||
@@ -222,25 +222,28 @@ class CompilerReferenceReader {
|
||||
}
|
||||
|
||||
@Nullable("return null if the class hierarchy contains ambiguous qualified names")
|
||||
LightRef.NamedLightRef[] getWholeHierarchy(LightRef.LightClassHierarchyElementDef hierarchyElement, boolean checkBaseClassAmbiguity, int interruptNumber) {
|
||||
LightRef.LightClassHierarchyElementDef[] getHierarchy(LightRef.LightClassHierarchyElementDef hierarchyElement,
|
||||
boolean checkBaseClassAmbiguity,
|
||||
boolean includeAnonymous,
|
||||
int interruptNumber) {
|
||||
try {
|
||||
Set<LightRef.NamedLightRef> result = new THashSet<>();
|
||||
Queue<LightRef.NamedLightRef> q = new Queue<>(10);
|
||||
Set<LightRef.LightClassHierarchyElementDef> result = new THashSet<>();
|
||||
Queue<LightRef.LightClassHierarchyElementDef> q = new Queue<>(10);
|
||||
q.addLast(hierarchyElement);
|
||||
while (!q.isEmpty()) {
|
||||
LightRef.NamedLightRef curClass = q.pullFirst();
|
||||
LightRef.LightClassHierarchyElementDef curClass = q.pullFirst();
|
||||
if (interruptNumber != -1 && result.size() > interruptNumber) {
|
||||
break;
|
||||
}
|
||||
if (result.add(curClass)) {
|
||||
if (checkBaseClassAmbiguity || curClass != hierarchyElement) {
|
||||
if (!(curClass instanceof LightRef.LightAnonymousClassDef) && (checkBaseClassAmbiguity || curClass != hierarchyElement)) {
|
||||
if (hasMultipleDefinitions(curClass)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
myIndex.get(CompilerIndices.BACK_HIERARCHY).getData(curClass).forEach((id, children) -> {
|
||||
for (LightRef child : children) {
|
||||
if (child instanceof LightRef.LightClassHierarchyElementDef && !(child instanceof LightRef.LightAnonymousClassDef)) {
|
||||
if (child instanceof LightRef.LightClassHierarchyElementDef && (includeAnonymous || !(child instanceof LightRef.LightAnonymousClassDef))) {
|
||||
q.addLast((LightRef.LightClassHierarchyElementDef)child);
|
||||
}
|
||||
}
|
||||
@@ -248,13 +251,27 @@ class CompilerReferenceReader {
|
||||
});
|
||||
}
|
||||
}
|
||||
return result.toArray(LightRef.NamedLightRef.EMPTY_ARRAY);
|
||||
return result.toArray(LightRef.LightClassHierarchyElementDef.EMPTY_ARRAY);
|
||||
}
|
||||
catch (StorageException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
LightRef.LightClassHierarchyElementDef[] getDirectInheritors(LightRef.LightClassHierarchyElementDef hierarchyElement) throws StorageException {
|
||||
Set<LightRef.LightClassHierarchyElementDef> result = new THashSet<>();
|
||||
myIndex.get(CompilerIndices.BACK_HIERARCHY).getData(hierarchyElement).forEach((id, children) -> {
|
||||
for (LightRef child : children) {
|
||||
if (child instanceof LightRef.LightClassHierarchyElementDef && !(child instanceof LightRef.LightAnonymousClassDef)) {
|
||||
result.add((LightRef.LightClassHierarchyElementDef)child);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result.toArray(LightRef.LightClassHierarchyElementDef.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
private enum DefCount { NONE, ONE, MANY}
|
||||
private boolean hasMultipleDefinitions(LightRef.NamedLightRef def) throws StorageException {
|
||||
DefCount[] result = new DefCount[]{DefCount.NONE};
|
||||
|
||||
+8
-1
@@ -25,7 +25,7 @@ import org.jetbrains.jps.backwardRefs.SignatureData;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* The service is used for java relevant chain completion
|
||||
* The service is used for java relevant chain completion / frequently used superclass inspection
|
||||
*/
|
||||
public abstract class CompilerReferenceServiceEx extends CompilerReferenceService {
|
||||
protected CompilerReferenceServiceEx(Project project) {
|
||||
@@ -43,4 +43,11 @@ public abstract class CompilerReferenceServiceEx extends CompilerReferenceServic
|
||||
@NotNull
|
||||
public abstract String getName(int idx)
|
||||
throws ReferenceIndexUnavailableException;
|
||||
|
||||
public abstract int getNameId(@NotNull String name) throws ReferenceIndexUnavailableException;
|
||||
|
||||
@NotNull
|
||||
public abstract LightRef.LightClassHierarchyElementDef[] getDirectInheritors(LightRef.LightClassHierarchyElementDef baseClass) throws ReferenceIndexUnavailableException;
|
||||
|
||||
public abstract int getInheritorCount(LightRef.LightClassHierarchyElementDef baseClass) throws ReferenceIndexUnavailableException;
|
||||
}
|
||||
|
||||
+55
-2
@@ -53,6 +53,7 @@ import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiModificationTracker;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import com.intellij.util.indexing.StorageException;
|
||||
@@ -235,7 +236,7 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
|
||||
.filter(r -> r instanceof LightRef.JavaLightMethodRef)
|
||||
.map(r -> (LightRef.JavaLightMethodRef) r)
|
||||
.flatMap(r -> {
|
||||
LightRef.NamedLightRef[] hierarchy = myReader.getWholeHierarchy(r.getOwner(), false, ChainSearchMagicConstants.MAX_HIERARCHY_SIZE);
|
||||
LightRef.NamedLightRef[] hierarchy = myReader.getHierarchy(r.getOwner(), false, false, ChainSearchMagicConstants.MAX_HIERARCHY_SIZE);
|
||||
return hierarchy == null ? Stream.empty() : Arrays.stream(hierarchy).map(c -> r.override(c.getName()));
|
||||
})
|
||||
.distinct()
|
||||
@@ -278,7 +279,7 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
|
||||
return false;
|
||||
}
|
||||
catch (Exception e) {
|
||||
onException(e, "correlation");
|
||||
onException(e, "conditional probability");
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
@@ -293,6 +294,58 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceServiceEx imp
|
||||
try {
|
||||
if (myReader == null) throw new ReferenceIndexUnavailableException();
|
||||
return myReader.getNameEnumerator().getName(idx);
|
||||
} catch (Exception e) {
|
||||
onException(e, "find methods");
|
||||
throw new ReferenceIndexUnavailableException();
|
||||
} finally {
|
||||
myReadDataLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNameId(@NotNull String name) throws ReferenceIndexUnavailableException {
|
||||
myReadDataLock.lock();
|
||||
try {
|
||||
if (myReader == null) throw new ReferenceIndexUnavailableException();
|
||||
int id;
|
||||
try {
|
||||
id = myReader.getNameEnumerator().tryEnumerate(name);
|
||||
}
|
||||
catch (Exception e) {
|
||||
onException(e, "get name-id");
|
||||
throw new ReferenceIndexUnavailableException();
|
||||
}
|
||||
return id;
|
||||
} finally {
|
||||
myReadDataLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LightRef.LightClassHierarchyElementDef[] getDirectInheritors(@NotNull LightRef.LightClassHierarchyElementDef baseClass) throws ReferenceIndexUnavailableException {
|
||||
myReadDataLock.lock();
|
||||
try {
|
||||
if (myReader == null) throw new ReferenceIndexUnavailableException();
|
||||
return myReader.getDirectInheritors(baseClass);
|
||||
} catch (Exception e) {
|
||||
onException(e, "find methods");
|
||||
throw new ReferenceIndexUnavailableException();
|
||||
} finally {
|
||||
myReadDataLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInheritorCount(@NotNull LightRef.LightClassHierarchyElementDef baseClass) throws ReferenceIndexUnavailableException {
|
||||
myReadDataLock.lock();
|
||||
try {
|
||||
if (myReader == null) throw new ReferenceIndexUnavailableException();
|
||||
LightRef.NamedLightRef[] hierarchy = ObjectUtils.notNull(myReader.getHierarchy(baseClass, false, true, -1));
|
||||
return hierarchy.length;
|
||||
} catch (Exception e) {
|
||||
onException(e, "inheritor count");
|
||||
throw new ReferenceIndexUnavailableException();
|
||||
} finally {
|
||||
myReadDataLock.unlock();
|
||||
}
|
||||
|
||||
+34
-35
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,34 +13,37 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.inheritance;
|
||||
package com.intellij.compiler.inspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.codeInsight.intention.LowPriorityAction;
|
||||
import com.intellij.codeInsight.intention.HighPriorityAction;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
public class ChangeSuperClassFix implements LocalQuickFix {
|
||||
public class ChangeSuperClassFix implements LocalQuickFix, HighPriorityAction {
|
||||
@NotNull
|
||||
private final SmartPsiElementPointer<PsiClass> myNewSuperClass;
|
||||
@NotNull
|
||||
private final SmartPsiElementPointer<PsiClass> myOldSuperClass;
|
||||
private final int myPercent;
|
||||
private final int myInheritorCount;
|
||||
@NotNull
|
||||
private final String myNewSuperName;
|
||||
private final boolean myNewSuperIsInterface;
|
||||
|
||||
public ChangeSuperClassFix(@NotNull final PsiClass newSuperClass, final int percent, @NotNull final PsiClass oldSuperClass) {
|
||||
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(newSuperClass.getProject());
|
||||
myNewSuperName = ObjectUtils.notNull(newSuperClass.getQualifiedName());
|
||||
myNewSuperIsInterface = newSuperClass.isInterface();
|
||||
myNewSuperClass = smartPointerManager.createSmartPsiElementPointer(newSuperClass);
|
||||
myOldSuperClass = smartPointerManager.createSmartPsiElementPointer(oldSuperClass);
|
||||
myPercent = percent;
|
||||
myInheritorCount = percent;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -50,14 +53,14 @@ public class ChangeSuperClassFix implements LocalQuickFix {
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public int getPercent() {
|
||||
return myPercent;
|
||||
public int getInheritorCount() {
|
||||
return myInheritorCount;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return String.format("Make extends '%s' - %s%%", myNewSuperName, myPercent);
|
||||
return String.format("Make " + (myNewSuperIsInterface ? "implements" : "extends") + " '%s'", myNewSuperName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -83,42 +86,38 @@ public class ChangeSuperClassFix implements LocalQuickFix {
|
||||
private static void changeSuperClass(@NotNull final PsiClass aClass,
|
||||
@NotNull final PsiClass oldSuperClass,
|
||||
@NotNull final PsiClass newSuperClass) {
|
||||
PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();
|
||||
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(aClass.getProject());
|
||||
PsiElementFactory factory = psiFacade.getElementFactory();
|
||||
if (aClass instanceof PsiAnonymousClass) {
|
||||
((PsiAnonymousClass)aClass).getBaseClassReference().replace(factory.createClassReferenceElement(newSuperClass));
|
||||
return;
|
||||
}
|
||||
else if (oldSuperClass.isInterface()) {
|
||||
final PsiReferenceList interfaceList = aClass.getImplementsList();
|
||||
if (interfaceList != null) {
|
||||
for (final PsiJavaCodeReferenceElement interfaceRef : interfaceList.getReferenceElements()) {
|
||||
final PsiElement aInterface = interfaceRef.resolve();
|
||||
if (aInterface != null && aInterface.isEquivalentTo(oldSuperClass)) {
|
||||
interfaceRef.delete();
|
||||
}
|
||||
}
|
||||
PsiReferenceList extendsList = ObjectUtils.notNull(aClass.getExtendsList());
|
||||
PsiJavaCodeReferenceElement[] refElements =
|
||||
ArrayUtil.mergeArrays(getReferences(extendsList), getReferences(aClass.getImplementsList()));
|
||||
for (PsiJavaCodeReferenceElement refElement : refElements) {
|
||||
if (refElement.isReferenceTo(oldSuperClass)) {
|
||||
refElement.delete();
|
||||
}
|
||||
}
|
||||
|
||||
final PsiReferenceList extendsList = aClass.getExtendsList();
|
||||
if (extendsList != null) {
|
||||
final PsiJavaCodeReferenceElement newClassReference = factory.createClassReferenceElement(newSuperClass);
|
||||
if (extendsList.getReferenceElements().length == 0) {
|
||||
extendsList.add(newClassReference);
|
||||
}
|
||||
}
|
||||
PsiReferenceList list;
|
||||
if (newSuperClass.isInterface()) {
|
||||
list = aClass.getImplementsList();
|
||||
}
|
||||
else {
|
||||
final PsiReferenceList extendsList = aClass.getExtendsList();
|
||||
if (extendsList != null && extendsList.getReferenceElements().length == 1) {
|
||||
extendsList.getReferenceElements()[0].delete();
|
||||
PsiElement ref = extendsList.add(factory.createClassReferenceElement(newSuperClass));
|
||||
JavaCodeStyleManager.getInstance(aClass.getProject()).shortenClassReferences(ref);
|
||||
list = extendsList;
|
||||
PsiJavaCodeReferenceElement[] elements = list.getReferenceElements();
|
||||
if (elements.length == 1 &&
|
||||
elements[0].isReferenceTo(psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, aClass.getResolveScope()))) {
|
||||
elements[0].delete();
|
||||
}
|
||||
}
|
||||
PsiElement ref = list.add(factory.createClassReferenceElement(newSuperClass));
|
||||
JavaCodeStyleManager.getInstance(aClass.getProject()).shortenClassReferences(ref);
|
||||
}
|
||||
|
||||
public static class LowPriority extends ChangeSuperClassFix implements LowPriorityAction {
|
||||
public LowPriority(@NotNull final PsiClass newSuperClass, final int percent, @NotNull final PsiClass oldSuperClass) {
|
||||
super(newSuperClass, percent, oldSuperClass);
|
||||
}
|
||||
private static PsiJavaCodeReferenceElement[] getReferences(PsiReferenceList list) {
|
||||
return list == null ? PsiJavaCodeReferenceElement.EMPTY_ARRAY : list.getReferenceElements();
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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.intellij.compiler.inspection;
|
||||
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.compiler.CompilerReferenceService;
|
||||
import com.intellij.compiler.backwardRefs.CompilerReferenceServiceEx;
|
||||
import com.intellij.compiler.backwardRefs.ReferenceIndexUnavailableException;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.java.stubs.index.JavaFullClassNameIndex;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import one.util.streamex.MoreCollectors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.backwardRefs.LightRef;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FrequentlyUsedInheritorInspection extends BaseJavaLocalInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance(FrequentlyUsedInheritorInspection.class);
|
||||
|
||||
public static final byte MAX_RESULT = 3;
|
||||
private static final int PERCENT_THRESHOLD = 20;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ProblemDescriptor[] checkClass(@NotNull final PsiClass aClass,
|
||||
@NotNull final InspectionManager manager,
|
||||
final boolean isOnTheFly) {
|
||||
if (aClass.isInterface() || aClass instanceof PsiTypeParameter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiClass superClass = getSuperIfOnlyOne(aClass);
|
||||
if (superClass == null) return null;
|
||||
|
||||
long ms = System.currentTimeMillis();
|
||||
final List<ClassAndInheritorCount> topInheritors = getTopInheritorsUsingCompilerIndices(superClass, aClass.getResolveScope(), aClass);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("search for inheritance structure of " + superClass.getQualifiedName() + " in " + (System.currentTimeMillis() - ms) + " ms");
|
||||
}
|
||||
if (topInheritors.isEmpty()) return null;
|
||||
|
||||
final Collection<LocalQuickFix> topInheritorsQuickFix = new ArrayList<>(topInheritors.size());
|
||||
for (final ClassAndInheritorCount searchResult : topInheritors) {
|
||||
final LocalQuickFix quickFix = new ChangeSuperClassFix(searchResult.psi, searchResult.number, superClass);
|
||||
topInheritorsQuickFix.add(quickFix);
|
||||
if (topInheritorsQuickFix.size() >= MAX_RESULT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new ProblemDescriptor[]{manager
|
||||
.createProblemDescriptor(aClass, "Class can have more common super class", isOnTheFly,
|
||||
topInheritorsQuickFix.toArray(LocalQuickFix.EMPTY_ARRAY),
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING)};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiClass getSuperIfOnlyOne(@NotNull final PsiClass aClass) {
|
||||
PsiClass superClass = aClass.getSuperClass();
|
||||
if (superClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) {
|
||||
return isInSourceContent(aClass) ? superClass : null;
|
||||
}
|
||||
|
||||
return Arrays
|
||||
.stream(aClass.getInterfaces())
|
||||
.filter(c -> !CommonClassNames.JAVA_LANG_OBJECT.equals(c.getQualifiedName()))
|
||||
.filter(c -> isInSourceContent(c))
|
||||
.collect(MoreCollectors.onlyOne())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<ClassAndInheritorCount> getTopInheritorsUsingCompilerIndices(@NotNull PsiClass aClass,
|
||||
@NotNull GlobalSearchScope searchScope,
|
||||
@NotNull PsiElement place) {
|
||||
String qName = aClass.getQualifiedName();
|
||||
if (qName == null) return Collections.emptyList();
|
||||
|
||||
final Project project = aClass.getProject();
|
||||
final CompilerReferenceServiceEx compilerRefService = (CompilerReferenceServiceEx)CompilerReferenceService.getInstance(project);
|
||||
try {
|
||||
int id = compilerRefService.getNameId(qName);
|
||||
if (id == 0) return Collections.emptyList();
|
||||
return findInheritors(aClass, new LightRef.JavaLightClassRef(id), searchScope, place, -1, project, compilerRefService);
|
||||
}
|
||||
catch (ReferenceIndexUnavailableException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ClassAndInheritorCount> findInheritors(@NotNull PsiClass aClass,
|
||||
@NotNull LightRef.JavaLightClassRef classAsLightRef,
|
||||
@NotNull GlobalSearchScope searchScope,
|
||||
@NotNull PsiElement place,
|
||||
int hierarchyCardinality,
|
||||
@NotNull Project project,
|
||||
@NotNull CompilerReferenceServiceEx compilerRefService) {
|
||||
LightRef.LightClassHierarchyElementDef[] directInheritors = compilerRefService.getDirectInheritors(classAsLightRef);
|
||||
|
||||
if (hierarchyCardinality == -1) {
|
||||
hierarchyCardinality = compilerRefService.getInheritorCount(classAsLightRef);
|
||||
}
|
||||
int finalHierarchyCardinality = hierarchyCardinality;
|
||||
|
||||
List<ClassAndInheritorCount> directInheritorStats = Stream
|
||||
.of(directInheritors)
|
||||
.filter(inheritor -> !(inheritor instanceof LightRef.LightAnonymousClassDef))
|
||||
.map(inheritor -> {
|
||||
int count = compilerRefService.getInheritorCount(inheritor);
|
||||
if (count * 100 > finalHierarchyCardinality * PERCENT_THRESHOLD) {
|
||||
return new Object() {
|
||||
final LightRef.LightClassHierarchyElementDef myDef = inheritor;
|
||||
final int inheritorCount = count;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(defAndCount -> {
|
||||
String name = compilerRefService.getName(defAndCount.myDef.getName());
|
||||
PsiClass inheritor =
|
||||
JavaFullClassNameIndex.getInstance().get(name.hashCode(), project, searchScope).stream()
|
||||
.filter(cls -> name.equals(cls.getQualifiedName()))
|
||||
.collect(MoreCollectors.onlyOne())
|
||||
.orElse(null);
|
||||
|
||||
if (inheritor == null || !inheritor.isInheritor(aClass, false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ClassAndInheritorCount(inheritor, defAndCount.myDef, defAndCount.inheritorCount);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
PsiResolveHelper resolveHelper = PsiResolveHelper.SERVICE.getInstance(project);
|
||||
return directInheritorStats
|
||||
.stream()
|
||||
.filter(c -> resolveHelper.isAccessible(c.psi, place, null))
|
||||
.flatMap(c -> Stream.concat(Stream.of(c), getClassesIfInterface(c, finalHierarchyCardinality, searchScope, place, project, compilerRefService).stream()))
|
||||
.sorted()
|
||||
.limit(MAX_RESULT)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<ClassAndInheritorCount> getClassesIfInterface(@NotNull ClassAndInheritorCount classAndInheritorCount,
|
||||
int hierarchyCardinality,
|
||||
GlobalSearchScope searchScope,
|
||||
PsiElement place,
|
||||
Project project,
|
||||
CompilerReferenceServiceEx compilerRefService) {
|
||||
if (classAndInheritorCount.psi.isInterface()) {
|
||||
return findInheritors(classAndInheritorCount.psi,
|
||||
(LightRef.JavaLightClassRef)classAndInheritorCount.descriptor,
|
||||
searchScope,
|
||||
place,
|
||||
hierarchyCardinality,
|
||||
project,
|
||||
compilerRefService);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static boolean isInSourceContent(@NotNull PsiElement e) {
|
||||
final VirtualFile file = e.getContainingFile().getVirtualFile();
|
||||
if (file == null) return false;
|
||||
final ProjectFileIndex index = ProjectRootManager.getInstance(e.getProject()).getFileIndex();
|
||||
return index.isInContent(file);
|
||||
}
|
||||
|
||||
private static class ClassAndInheritorCount implements Comparable<ClassAndInheritorCount> {
|
||||
private final PsiClass psi;
|
||||
private final LightRef.LightClassHierarchyElementDef descriptor;
|
||||
private final int number;
|
||||
|
||||
private ClassAndInheritorCount(PsiClass psi,
|
||||
LightRef.LightClassHierarchyElementDef descriptor,
|
||||
int number) {
|
||||
this.psi = psi;
|
||||
this.descriptor = descriptor;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClassAndInheritorCount o) {
|
||||
return - number + o.number;
|
||||
}
|
||||
}
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
package com.intellij.codeInspection.inheritance;
|
||||
|
||||
import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.inheritance.search.InheritorsStatisticalDataSearch;
|
||||
import com.intellij.codeInspection.inheritance.search.InheritorsStatisticsSearchResult;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class SuperClassHasFrequentlyUsedInheritorsInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
private static final int MIN_PERCENT_RATIO = 5;
|
||||
public static final int MAX_QUICK_FIX_COUNTS = 4;
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getGroupDisplayName() {
|
||||
return GroupNames.INHERITANCE_GROUP_NAME;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Class may extend a commonly used base class instead of implementing interface or extending abstract class";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ProblemDescriptor[] checkClass(@NotNull final PsiClass aClass,
|
||||
@NotNull final InspectionManager manager,
|
||||
final boolean isOnTheFly) {
|
||||
if (aClass.isInterface() ||
|
||||
aClass.isEnum() ||
|
||||
aClass instanceof PsiTypeParameter ||
|
||||
aClass.getMethods().length != 0 ||
|
||||
aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiClass superClass = getSuperIfUnique(aClass);
|
||||
if (superClass == null) return null;
|
||||
|
||||
final List<InheritorsStatisticsSearchResult> topInheritors =
|
||||
InheritorsStatisticalDataSearch.search(superClass, aClass, aClass.getResolveScope(), MIN_PERCENT_RATIO);
|
||||
|
||||
if (topInheritors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Collection<LocalQuickFix> topInheritorsQuickFix = new ArrayList<>(topInheritors.size());
|
||||
|
||||
boolean isFirst = true;
|
||||
for (final InheritorsStatisticsSearchResult searchResult : topInheritors) {
|
||||
final LocalQuickFix quickFix;
|
||||
if (isFirst) {
|
||||
quickFix = new ChangeSuperClassFix(searchResult.getPsiClass(), searchResult.getPercent(), superClass);
|
||||
isFirst = false;
|
||||
} else {
|
||||
quickFix = new ChangeSuperClassFix.LowPriority(searchResult.getPsiClass(), searchResult.getPercent(), superClass);
|
||||
}
|
||||
topInheritorsQuickFix.add(quickFix);
|
||||
if (topInheritorsQuickFix.size() >= MAX_QUICK_FIX_COUNTS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new ProblemDescriptor[]{
|
||||
manager.createProblemDescriptor(aClass, getDisplayName(), false, ProblemHighlightType.INFORMATION, false,
|
||||
topInheritorsQuickFix.toArray(new LocalQuickFix[topInheritorsQuickFix.size()]))};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiClass getSuperIfUnique(@NotNull final PsiClass aClass) {
|
||||
if (aClass instanceof PsiAnonymousClass) {
|
||||
final PsiClass returnClass = (PsiClass)((PsiAnonymousClass)aClass).getBaseClassReference().resolve();
|
||||
if (returnClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(returnClass.getQualifiedName())) return null;
|
||||
return returnClass;
|
||||
}
|
||||
final PsiReferenceList extendsList = aClass.getExtendsList();
|
||||
if (extendsList != null) {
|
||||
final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements();
|
||||
if (referenceElements.length == 1) {
|
||||
final PsiElement resolved = referenceElements[0].resolve();
|
||||
if (resolved instanceof PsiClass) {
|
||||
PsiClass returnClass = (PsiClass)resolved;
|
||||
if (!CommonClassNames.JAVA_LANG_OBJECT.equals(returnClass.getQualifiedName()) && !returnClass.isInterface()) {
|
||||
return returnClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final PsiReferenceList implementsList = aClass.getImplementsList();
|
||||
if (implementsList != null) {
|
||||
final PsiJavaCodeReferenceElement[] referenceElements = implementsList.getReferenceElements();
|
||||
if (referenceElements.length == 1) {
|
||||
PsiClass returnClass = (PsiClass)referenceElements[0].resolve();
|
||||
if (returnClass != null && returnClass.isInterface()) {
|
||||
return returnClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
package com.intellij.codeInspection.inheritance.search;
|
||||
|
||||
import com.intellij.psi.PsiClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class InheritorsCountData implements Comparable<InheritorsCountData> {
|
||||
@NotNull
|
||||
private final PsiClass myPsiClass;
|
||||
private final int myInheritorsCount;
|
||||
|
||||
public InheritorsCountData(@NotNull final PsiClass psiClass, final int inheritorsCount) {
|
||||
myPsiClass = psiClass;
|
||||
myInheritorsCount = inheritorsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof InheritorsCountData)) return false;
|
||||
|
||||
final InheritorsCountData data = (InheritorsCountData)o;
|
||||
return myInheritorsCount == data.myInheritorsCount && myPsiClass.equals(data.myPsiClass);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiClass getPsiClass() {
|
||||
return myPsiClass;
|
||||
}
|
||||
|
||||
public int getInheritorsCount() {
|
||||
return myInheritorsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final String name = myPsiClass.getName();
|
||||
int result = name != null ? name.hashCode() : 0;
|
||||
return 31 * result + myInheritorsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull final InheritorsCountData that) {
|
||||
final int sub = -this.myInheritorsCount + that.myInheritorsCount;
|
||||
if (sub != 0) return sub;
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(this.myPsiClass.getName(), that.myPsiClass.getName());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s:%d", myPsiClass, myInheritorsCount);
|
||||
}
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.intellij.codeInspection.inheritance.search;
|
||||
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.util.Processor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class InheritorsStatisticalDataSearch {
|
||||
|
||||
/**
|
||||
* search for most used inheritors of superClass in scope
|
||||
*
|
||||
* @param aClass - class that excluded from inheritors of superClass
|
||||
* @param minPercentRatio - head volume
|
||||
* @return - search results in relevant ordering (frequency descent)
|
||||
*/
|
||||
public static List<InheritorsStatisticsSearchResult> search(final @NotNull PsiClass superClass,
|
||||
final @NotNull PsiClass aClass,
|
||||
final @NotNull GlobalSearchScope scope,
|
||||
final int minPercentRatio) {
|
||||
final String superClassName = superClass.getName();
|
||||
final String aClassName = aClass.getName();
|
||||
final Set<String> disabledNames = new HashSet<>();
|
||||
disabledNames.add(aClassName);
|
||||
disabledNames.add(superClassName);
|
||||
final Set<InheritorsCountData> collector = new TreeSet<>();
|
||||
final Couple<Integer> collectingResult = collectInheritorsInfo(superClass, collector, disabledNames);
|
||||
final int allAnonymousInheritors = collectingResult.getSecond();
|
||||
final int allInheritors = collectingResult.getFirst() + allAnonymousInheritors - 1;
|
||||
|
||||
final List<InheritorsStatisticsSearchResult> result = new ArrayList<>();
|
||||
|
||||
Integer firstPercent = null;
|
||||
for (final InheritorsCountData data : collector) {
|
||||
final int inheritorsCount = data.getInheritorsCount();
|
||||
if (inheritorsCount < allAnonymousInheritors) {
|
||||
break;
|
||||
}
|
||||
final int percent = (inheritorsCount * 100) / allInheritors;
|
||||
if (percent < 1) {
|
||||
break;
|
||||
}
|
||||
if (firstPercent == null) {
|
||||
firstPercent = percent;
|
||||
}
|
||||
else if (percent * minPercentRatio < firstPercent) {
|
||||
break;
|
||||
}
|
||||
|
||||
final PsiClass psiClass = data.getPsiClass();
|
||||
final VirtualFile file = psiClass.getContainingFile().getVirtualFile();
|
||||
if (file != null && scope.contains(file)) {
|
||||
result.add(new InheritorsStatisticsSearchResult(psiClass, percent));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Couple<Integer> collectInheritorsInfo(final PsiClass superClass,
|
||||
final Set<InheritorsCountData> collector,
|
||||
final Set<String> disabledNames) {
|
||||
return collectInheritorsInfo(superClass, collector, disabledNames, new HashSet<>(), new HashSet<>());
|
||||
}
|
||||
|
||||
private static Couple<Integer> collectInheritorsInfo(final PsiClass aClass,
|
||||
final Set<InheritorsCountData> collector,
|
||||
final Set<String> disabledNames,
|
||||
final Set<String> processedElements,
|
||||
final Set<String> allNotAnonymousInheritors) {
|
||||
final String className = aClass.getName();
|
||||
if (!processedElements.add(className)) return Couple.of(0, 0);
|
||||
|
||||
final MyInheritorsInfoProcessor processor = new MyInheritorsInfoProcessor(collector, disabledNames, processedElements);
|
||||
DirectClassInheritorsSearch.search(aClass).forEach(processor);
|
||||
|
||||
allNotAnonymousInheritors.addAll(processor.getAllNotAnonymousInheritors());
|
||||
|
||||
final int allInheritorsCount = processor.getAllNotAnonymousInheritors().size() + processor.getAnonymousInheritorsCount();
|
||||
if (!aClass.isInterface() && allInheritorsCount != 0 && !disabledNames.contains(className)) {
|
||||
collector.add(new InheritorsCountData(aClass, allInheritorsCount));
|
||||
}
|
||||
return Couple.of(allNotAnonymousInheritors.size(), processor.getAnonymousInheritorsCount());
|
||||
}
|
||||
|
||||
private static class MyInheritorsInfoProcessor implements Processor<PsiClass> {
|
||||
private final Set<InheritorsCountData> myCollector;
|
||||
private final Set<String> myDisabledNames;
|
||||
private final Set<String> myProcessedElements;
|
||||
private final Set<String> myAllNotAnonymousInheritors;
|
||||
|
||||
private MyInheritorsInfoProcessor(Set<InheritorsCountData> collector, Set<String> disabledNames, Set<String> processedElements) {
|
||||
myCollector = collector;
|
||||
myDisabledNames = disabledNames;
|
||||
myProcessedElements = processedElements;
|
||||
myAllNotAnonymousInheritors = new HashSet<>();
|
||||
}
|
||||
|
||||
private int myAnonymousInheritorsCount;
|
||||
|
||||
private Set<String> getAllNotAnonymousInheritors() {
|
||||
return myAllNotAnonymousInheritors;
|
||||
}
|
||||
|
||||
private int getAnonymousInheritorsCount() {
|
||||
return myAnonymousInheritorsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(final PsiClass psiClass) {
|
||||
final String inheritorName = psiClass.getName();
|
||||
if (inheritorName == null) {
|
||||
myAnonymousInheritorsCount++;
|
||||
}
|
||||
else {
|
||||
final Couple<Integer> res = collectInheritorsInfo(psiClass,
|
||||
myCollector,
|
||||
myDisabledNames,
|
||||
myProcessedElements,
|
||||
myAllNotAnonymousInheritors);
|
||||
myAnonymousInheritorsCount += res.getSecond();
|
||||
if (!psiClass.isInterface()) {
|
||||
myAllNotAnonymousInheritors.add(inheritorName);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package com.intellij.codeInspection.inheritance.search;
|
||||
|
||||
import com.intellij.psi.PsiClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class InheritorsStatisticsSearchResult {
|
||||
|
||||
@NotNull
|
||||
private final PsiClass myClass;
|
||||
private final int myPercent;
|
||||
|
||||
InheritorsStatisticsSearchResult(final @NotNull PsiClass aClass, final int percent) {
|
||||
myClass = aClass;
|
||||
myPercent = percent;
|
||||
}
|
||||
|
||||
public PsiClass getPsiClass() {
|
||||
return myClass;
|
||||
}
|
||||
|
||||
public int getPercent() {
|
||||
return myPercent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
class Some {
|
||||
void m() {
|
||||
A someA = new A () {}<caret>
|
||||
A someA = new A<caret> () {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ class A {}
|
||||
class B extends A {}
|
||||
|
||||
class B1 extends B {}
|
||||
class B2 extends B {}
|
||||
class B3 extends B {}
|
||||
class B4 extends B {}
|
||||
class B5 extends B {}
|
||||
class B3 extends B {}
|
||||
class B6 extends B {}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
class MyInheritor implement<caret>s A {
|
||||
|
||||
}
|
||||
|
||||
interface A {
|
||||
}
|
||||
|
||||
interface B extends A {}
|
||||
interface B1 extends A {}
|
||||
interface B6 extends A {}
|
||||
interface B2 extends A {}
|
||||
interface B3 extends A {}
|
||||
interface B4 extends A {}
|
||||
interface B5 extends A {}
|
||||
|
||||
interface C extends B {}
|
||||
interface C1 extends B {}
|
||||
interface C2 extends B {}
|
||||
interface C3 extends B {}
|
||||
interface C4 extends B {}
|
||||
|
||||
class D extends C {}
|
||||
@@ -1,25 +0,0 @@
|
||||
class MyInheritor implement<caret>s A {}
|
||||
|
||||
interface A {}
|
||||
|
||||
interface B extends A {}
|
||||
interface B1 extends A {}
|
||||
interface B6 extends A {}
|
||||
interface B2 extends A {}
|
||||
interface B3 extends A {}
|
||||
interface B4 extends A {}
|
||||
interface B5 extends A {}
|
||||
|
||||
interface C extends B {}
|
||||
interface C1 extends B {}
|
||||
interface C2 extends B {}
|
||||
interface C3 extends B {}
|
||||
interface C4 extends B {}
|
||||
|
||||
class D implements C {}
|
||||
|
||||
class E1 extends D {}
|
||||
class E2 extends D {}
|
||||
class E3 extends D {}
|
||||
class E4 extends D {}
|
||||
class E5 extends D {}
|
||||
+2
-18
@@ -1,4 +1,4 @@
|
||||
class MyInheritor extends A {
|
||||
class MyInheritor<caret> extends A {
|
||||
|
||||
}
|
||||
|
||||
@@ -18,20 +18,4 @@ class C32 extends B3 {}
|
||||
|
||||
class B4 extends A {}
|
||||
class C41 extends B4 {}
|
||||
class C42 extends B4 {}
|
||||
|
||||
class B5 extends A {}
|
||||
class C51 extends B5 {}
|
||||
class C52 extends B5 {}
|
||||
|
||||
class B6 extends A {}
|
||||
class C61 extends B6 {}
|
||||
class C62 extends B6 {}
|
||||
|
||||
class B7 extends A {}
|
||||
class C71 extends B7 {}
|
||||
class C72 extends B7 {}
|
||||
|
||||
class B8 extends A {}
|
||||
class C81 extends B8 {}
|
||||
class C82 extends B8 {}
|
||||
class C42 extends B4 {}
|
||||
+22
-28
@@ -4,10 +4,11 @@ import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.IntentionActionDelegate;
|
||||
import com.intellij.codeInspection.ex.QuickFixWrapper;
|
||||
import com.intellij.codeInspection.inheritance.ChangeSuperClassFix;
|
||||
import com.intellij.codeInspection.inheritance.SuperClassHasFrequentlyUsedInheritorsInspection;
|
||||
import com.intellij.compiler.CompilerReferencesTestBase;
|
||||
import com.intellij.compiler.inspection.ChangeSuperClassFix;
|
||||
import com.intellij.compiler.inspection.FrequentlyUsedInheritorInspection;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.testFramework.SkipSlowTestLocally;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -15,37 +16,35 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class SuperClassHasFrequentlyUsedInheritorsInspectionTest extends JavaCodeInsightFixtureTestCase {
|
||||
@SkipSlowTestLocally
|
||||
public class FrequentlyUsedInheritorInspectionTest extends CompilerReferencesTestBase {
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
installCompiler();
|
||||
myFixture.enableInspections(FrequentlyUsedInheritorInspection.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return JavaTestUtil.getJavaTestDataPath() + "/inspection/smartInheritance/";
|
||||
}
|
||||
|
||||
//search tests
|
||||
|
||||
public void testRelevantClassShowed() {
|
||||
doTest(Pair.create("C", 75), Pair.create("B", 91));
|
||||
}
|
||||
|
||||
public void testInterfacesNotShowed() {
|
||||
assertEmptyResult();
|
||||
}
|
||||
|
||||
public void testInterfacesNotShowed2() {
|
||||
doTest(Pair.create("D", 83));
|
||||
doTest(Pair.create("B", 12));
|
||||
}
|
||||
|
||||
public void testAnonymousClasses() {
|
||||
doTest(Pair.create("B", 83));
|
||||
doTest(Pair.create("B", 7));
|
||||
}
|
||||
|
||||
public void testAnonymousClassesInStats() {
|
||||
doTest(Pair.create("A", 62));
|
||||
doTest(Pair.create("A", 6));
|
||||
}
|
||||
|
||||
public void testAbstractClass() {
|
||||
doTest(Pair.create("B", 85));
|
||||
doTest(Pair.create("B", 7));
|
||||
}
|
||||
|
||||
public void testNoCompletionForAbstractClasses() {
|
||||
@@ -53,31 +52,26 @@ public class SuperClassHasFrequentlyUsedInheritorsInspectionTest extends JavaCod
|
||||
}
|
||||
|
||||
public void testNoMoreThanMaxCountIntentions() {
|
||||
doTest(SuperClassHasFrequentlyUsedInheritorsInspection.MAX_QUICK_FIX_COUNTS);
|
||||
doTest(FrequentlyUsedInheritorInspection.MAX_RESULT);
|
||||
}
|
||||
|
||||
// completion tests
|
||||
|
||||
private void assertEmptyResult() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest(final Pair<String, Integer>... expectedResults) {
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
myFixture.enableInspections(SuperClassHasFrequentlyUsedInheritorsInspection.class);
|
||||
rebuildProject();
|
||||
|
||||
final Set<Pair<String, Integer>> actualSet = new HashSet<Pair<String, Integer>>();
|
||||
|
||||
for (Pair<String, Integer> pair : expectedResults) {
|
||||
IntentionAction action = myFixture.findSingleIntention("Make extends '" + pair.getFirst() +
|
||||
"' - " + pair.getSecond() +
|
||||
"%");
|
||||
|
||||
IntentionAction action = myFixture.findSingleIntention("Make extends '" + pair.getFirst());
|
||||
IntentionAction intentionAction = ((IntentionActionDelegate)action).getDelegate();
|
||||
if (intentionAction instanceof QuickFixWrapper) {
|
||||
ChangeSuperClassFix changeSuperClassFix = getQuickFixFromWrapper((QuickFixWrapper)intentionAction);
|
||||
if (changeSuperClassFix != null) {
|
||||
actualSet.add(Pair.create(changeSuperClassFix.getNewSuperClass().getQualifiedName(), changeSuperClassFix.getPercent()));
|
||||
actualSet.add(Pair.create(changeSuperClassFix.getNewSuperClass().getQualifiedName(), changeSuperClassFix.getInheritorCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ public class SuperClassHasFrequentlyUsedInheritorsInspectionTest extends JavaCod
|
||||
|
||||
private void doTest(final int expectedSize) {
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
myFixture.enableInspections(SuperClassHasFrequentlyUsedInheritorsInspection.class);
|
||||
rebuildProject();
|
||||
|
||||
List<IntentionAction> actions = myFixture.filterAvailableIntentions("Make extends '");
|
||||
|
||||
@@ -41,6 +41,8 @@ public interface LightRef extends RW.Savable {
|
||||
}
|
||||
|
||||
interface LightClassHierarchyElementDef extends NamedLightRef {
|
||||
LightClassHierarchyElementDef[] EMPTY_ARRAY = new LightClassHierarchyElementDef[0];
|
||||
|
||||
}
|
||||
|
||||
interface LightAnonymousClassDef extends LightClassHierarchyElementDef {
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public class ConvertInterfaceToClassIntention extends Intention {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void changeInterfaceToClass(PsiClass anInterface) throws IncorrectOperationException {
|
||||
public static void changeInterfaceToClass(PsiClass anInterface) throws IncorrectOperationException {
|
||||
final PsiIdentifier nameIdentifier = anInterface.getNameIdentifier();
|
||||
assert nameIdentifier != null;
|
||||
final PsiElement whiteSpace = nameIdentifier.getPrevSibling();
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection finds commonly used base class that could be extended instead of implementing interface or extending abstract class.
|
||||
For example: <code>java.util.List</code> could be implemented by inheriting from <code>java.util.AbstractList</code>.
|
||||
<!-- tooltip end -->
|
||||
The inspection works only if a project is built using IntelliJ IDEA build system and super class is located inside source files.
|
||||
</body>
|
||||
</html>
|
||||
@@ -815,11 +815,11 @@
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="MagicConstant" displayName="Magic Constant"
|
||||
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.magicConstant.MagicConstantInspection" />
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="SuperClassHasFrequentlyUsedInheritors"
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="FrequentlyUsedInheritorInspection"
|
||||
groupBundle="messages.InspectionsBundle"
|
||||
groupKey="group.names.inheritance.issues" enabledByDefault="false" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.inheritance.SuperClassHasFrequentlyUsedInheritorsInspection"
|
||||
displayName="Class may extend a commonly used base class instead of implementing interface"/>
|
||||
groupKey="group.names.inheritance.issues" enabledByDefault="false" level="INFORMATION"
|
||||
implementationClass="com.intellij.compiler.inspection.FrequentlyUsedInheritorInspection"
|
||||
displayName="Class may extend a commonly used base class"/>
|
||||
<localInspection language="UAST" shortName="ImplicitSubclassInspection"
|
||||
bundle="messages.InspectionsBundle" key="inspection.implicit.subclass.display.name"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.inheritance.issues"
|
||||
|
||||
Reference in New Issue
Block a user