diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java index a601ff96762a..9263ecd564ef 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceReader.java @@ -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 result = new THashSet<>(); - Queue q = new Queue<>(10); + Set result = new THashSet<>(); + Queue 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 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}; diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java index 4f8fc48eb6c9..2b6b9f733f96 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceEx.java @@ -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; } diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java index 76d5f860e7c8..c69df2ec92d8 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java @@ -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(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java b/java/compiler/impl/src/com/intellij/compiler/inspection/ChangeSuperClassFix.java similarity index 61% rename from java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java rename to java/compiler/impl/src/com/intellij/compiler/inspection/ChangeSuperClassFix.java index 1f3b2e8a837e..f3071f621436 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/ChangeSuperClassFix.java +++ b/java/compiler/impl/src/com/intellij/compiler/inspection/ChangeSuperClassFix.java @@ -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 myNewSuperClass; @NotNull private final SmartPsiElementPointer 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(); } } diff --git a/java/compiler/impl/src/com/intellij/compiler/inspection/FrequentlyUsedInheritorInspection.java b/java/compiler/impl/src/com/intellij/compiler/inspection/FrequentlyUsedInheritorInspection.java new file mode 100644 index 000000000000..3fb3f83c4c32 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/inspection/FrequentlyUsedInheritorInspection.java @@ -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 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 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 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 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 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 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 { + 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; + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java deleted file mode 100644 index d7c8e0aa4675..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/SuperClassHasFrequentlyUsedInheritorsInspection.java +++ /dev/null @@ -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 topInheritors = - InheritorsStatisticalDataSearch.search(superClass, aClass, aClass.getResolveScope(), MIN_PERCENT_RATIO); - - if (topInheritors.isEmpty()) { - return null; - } - - final Collection 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; - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java b/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java deleted file mode 100644 index 9f4a331e1516..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsCountData.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.intellij.codeInspection.inheritance.search; - -import com.intellij.psi.PsiClass; -import org.jetbrains.annotations.NotNull; - -class InheritorsCountData implements Comparable { - @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); - } -} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java b/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java deleted file mode 100644 index 567fa8c83b1f..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticalDataSearch.java +++ /dev/null @@ -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 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 disabledNames = new HashSet<>(); - disabledNames.add(aClassName); - disabledNames.add(superClassName); - final Set collector = new TreeSet<>(); - final Couple collectingResult = collectInheritorsInfo(superClass, collector, disabledNames); - final int allAnonymousInheritors = collectingResult.getSecond(); - final int allInheritors = collectingResult.getFirst() + allAnonymousInheritors - 1; - - final List 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 collectInheritorsInfo(final PsiClass superClass, - final Set collector, - final Set disabledNames) { - return collectInheritorsInfo(superClass, collector, disabledNames, new HashSet<>(), new HashSet<>()); - } - - private static Couple collectInheritorsInfo(final PsiClass aClass, - final Set collector, - final Set disabledNames, - final Set processedElements, - final Set 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 { - private final Set myCollector; - private final Set myDisabledNames; - private final Set myProcessedElements; - private final Set myAllNotAnonymousInheritors; - - private MyInheritorsInfoProcessor(Set collector, Set disabledNames, Set processedElements) { - myCollector = collector; - myDisabledNames = disabledNames; - myProcessedElements = processedElements; - myAllNotAnonymousInheritors = new HashSet<>(); - } - - private int myAnonymousInheritorsCount; - - private Set 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 res = collectInheritorsInfo(psiClass, - myCollector, - myDisabledNames, - myProcessedElements, - myAllNotAnonymousInheritors); - myAnonymousInheritorsCount += res.getSecond(); - if (!psiClass.isInterface()) { - myAllNotAnonymousInheritors.add(inheritorName); - } - } - return true; - } - } -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java b/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java deleted file mode 100644 index 8258be7c4de0..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/inheritance/search/InheritorsStatisticsSearchResult.java +++ /dev/null @@ -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; - } - -} diff --git a/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java b/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java index 789e79150a90..83d11cd8c75c 100644 --- a/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java +++ b/java/java-tests/testData/inspection/smartInheritance/AnonymousClasses.java @@ -1,6 +1,6 @@ class Some { void m() { - A someA = new A () {} + A someA = new A () {}; } } @@ -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 {} diff --git a/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java b/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java deleted file mode 100644 index d5ca8af6e15b..000000000000 --- a/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed.java +++ /dev/null @@ -1,22 +0,0 @@ -class MyInheritor implements 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 {} diff --git a/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java b/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java deleted file mode 100644 index 72e94b550f6d..000000000000 --- a/java/java-tests/testData/inspection/smartInheritance/InterfacesNotShowed2.java +++ /dev/null @@ -1,25 +0,0 @@ -class MyInheritor implements 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 {} diff --git a/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java b/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java index 7a6f476bfc79..685aa586dc50 100644 --- a/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java +++ b/java/java-tests/testData/inspection/smartInheritance/NoMoreThanMaxCountIntentions.java @@ -1,4 +1,4 @@ -class MyInheritor extends A { +class MyInheritor 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 {} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/FrequentlyUsedInheritorInspectionTest.java similarity index 66% rename from java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java rename to java/java-tests/testSrc/com/intellij/codeInspection/FrequentlyUsedInheritorInspectionTest.java index c20df1211858..b1967020d2ec 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/SuperClassHasFrequentlyUsedInheritorsInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/FrequentlyUsedInheritorInspectionTest.java @@ -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... expectedResults) { myFixture.configureByFile(getTestName(false) + ".java"); - myFixture.enableInspections(SuperClassHasFrequentlyUsedInheritorsInspection.class); + rebuildProject(); final Set> actualSet = new HashSet>(); for (Pair 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 actions = myFixture.filterAvailableIntentions("Make extends '"); diff --git a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/LightRef.java b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/LightRef.java index ffd5b592eb05..13d7905a13ce 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/LightRef.java +++ b/jps/jps-builders/src/org/jetbrains/jps/backwardRefs/LightRef.java @@ -41,6 +41,8 @@ public interface LightRef extends RW.Savable { } interface LightClassHierarchyElementDef extends NamedLightRef { + LightClassHierarchyElementDef[] EMPTY_ARRAY = new LightClassHierarchyElementDef[0]; + } interface LightAnonymousClassDef extends LightClassHierarchyElementDef { diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java index ffe2f5810205..8e4967744e19 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java @@ -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(); diff --git a/resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html b/resources-en/src/inspectionDescriptions/FrequentlyUsedInheritorInspection.html similarity index 51% rename from resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html rename to resources-en/src/inspectionDescriptions/FrequentlyUsedInheritorInspection.html index 02d667b2ec22..51e2e99314a5 100644 --- a/resources-en/src/inspectionDescriptions/SuperClassHasFrequentlyUsedInheritors.html +++ b/resources-en/src/inspectionDescriptions/FrequentlyUsedInheritorInspection.html @@ -1,6 +1,7 @@ This inspection finds commonly used base class that could be extended instead of implementing interface or extending abstract class. -For example: java.util.List could be implemented by inheriting from java.util.AbstractList. + +The inspection works only if a project is built using IntelliJ IDEA build system and super class is located inside source files. \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 54e3b4f00d9f..7b059d1e6316 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -815,11 +815,11 @@ - + groupKey="group.names.inheritance.issues" enabledByDefault="false" level="INFORMATION" + implementationClass="com.intellij.compiler.inspection.FrequentlyUsedInheritorInspection" + displayName="Class may extend a commonly used base class"/>