add a dev kit inspection: try to write stateless EP

This commit is contained in:
Sergey Ignatov
2015-11-02 15:28:29 +03:00
parent bdb7a4477d
commit 36dde4d19a
4 changed files with 132 additions and 6 deletions
@@ -129,6 +129,10 @@
<localInspection language="JAVA" shortName="UnsafeReturnStatementVisitor" displayName="Unsafe return statements visitor"
groupKey="inspections.group.name" enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.internal.UnsafeReturnStatementVisitorInspection" />
<localInspection language="JAVA" shortName="StatefulEp" displayName="Stateful EP"
groupKey="inspections.group.name"
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.StatefulEpInspection"/>
<moduleConfigurationEditorProvider implementation="org.jetbrains.idea.devkit.module.PluginModuleEditorsProvider"/>
<implicitUsageProvider implementation="org.jetbrains.idea.devkit.inspections.DevKitImplicitUsageProvider"/>
@@ -0,0 +1,5 @@
<html>
<body>
Are you sure about holding heavy objects in Extension Point implementation?
</body>
</html>
@@ -0,0 +1,98 @@
/*
* Copyright 2000-2015 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 org.jetbrains.idea.devkit.inspections;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.util.ExtensionPointCandidate;
import org.jetbrains.idea.devkit.util.ExtensionPointLocator;
import java.util.List;
public class StatefulEpInspection extends DevKitInspectionBase {
@Nullable
@Override
public ProblemDescriptor[] checkClass(@NotNull PsiClass psiClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
PsiField[] fields = psiClass.getFields();
if (fields.length == 0) return super.checkClass(psiClass, manager, isOnTheFly);
final boolean isQuickFix = InheritanceUtil.isInheritor(psiClass, LocalQuickFix.class.getCanonicalName());
if (isQuickFix || shouldCheck(psiClass)) {
List<ProblemDescriptor> result = ContainerUtil.newArrayList();
for (final PsiField field : fields) {
Checker projectChecker = new Checker(Project.class) {
@Override
boolean predicate() {
return !field.hasModifierProperty(PsiModifier.FINAL);
}
};
Checker psiChecker = new Checker(PsiElement.class) {
@NotNull
@Override
String getMessage() {
return "Potential memory leak: don't hold PsiElement, use SmartPsiElementPointer instead of" +
(isQuickFix ? "; also see LocalQuickFixOnPsiElement" : "");
}
};
Checker refChecker = new Checker(PsiReference.class);
for (Checker checker : new Checker[]{projectChecker, psiChecker, refChecker}) {
checker.check(field, manager, isOnTheFly, result);
}
}
return result.toArray(new ProblemDescriptor[result.size()]);
}
return super.checkClass(psiClass, manager, isOnTheFly);
}
boolean shouldCheck(@NotNull PsiClass psiClass) {
for (ExtensionPointCandidate candidate : new ExtensionPointLocator(psiClass).findSuperCandidates()) {
if (ExtensionPointLocator.isImplementedEp(psiClass, candidate)) return true;
}
return false;
}
private static class Checker {
@NotNull Class myClass;
private Checker(@NotNull Class psiClass) {
myClass = psiClass;
}
private void check(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly, @NotNull List<ProblemDescriptor> result) {
if (predicate() && InheritanceUtil.isInheritor(field.getType(), myClass.getCanonicalName())) {
String message = getMessage();
String actual = message.isEmpty() ? "Don't use " + myClass.getSimpleName() + " as a field in extension points" : message;
result.add(manager.createProblemDescriptor(field, actual, true, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly));
}
}
@NotNull
String getMessage() {
return "";
}
boolean predicate() {
return true;
}
}
}
@@ -27,6 +27,7 @@ import com.intellij.util.SmartList;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomService;
import com.intellij.util.xml.DomUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.dom.ExtensionPoint;
import org.jetbrains.idea.devkit.dom.IdeaPlugin;
@@ -70,13 +71,10 @@ public class ExtensionPointLocator {
private static void findExtensionPointCandidates(PsiClass psiClass, final List<ExtensionPointCandidate> list) {
String name = psiClass.getQualifiedName();
if (name == null) {
return;
}
if (name == null) return;
final Project project = psiClass.getProject();
final Collection<VirtualFile> candidates = DomService.getInstance().getDomFileCandidates(IdeaPlugin.class, project, GlobalSearchScope.allScope(project));
GlobalSearchScope scope = GlobalSearchScope.filesScope(project, candidates);
Project project = psiClass.getProject();
GlobalSearchScope scope = getCandidatesScope(project);
PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, new PsiNonJavaFileReferenceProcessor() {
@Override
public boolean process(PsiFile file, int startOffset, int endOffset) {
@@ -87,6 +85,27 @@ public class ExtensionPointLocator {
}, scope);
}
@NotNull
private static GlobalSearchScope getCandidatesScope(@NotNull Project project) {
Collection<VirtualFile> candidates = DomService.getInstance().getDomFileCandidates(IdeaPlugin.class, project, GlobalSearchScope.allScope(project));
return GlobalSearchScope.filesScope(project, candidates);
}
public static boolean isImplementedEp(@NotNull PsiClass psiClass, @NotNull final ExtensionPointCandidate candidate) {
String name = psiClass.getQualifiedName();
if (name == null) return false;
Project project = psiClass.getProject();
GlobalSearchScope scope = getCandidatesScope(project);
return !PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, new PsiNonJavaFileReferenceProcessor() {
@Override
public boolean process(PsiFile file, int startOffset, int endOffset) {
XmlTag tag = PsiTreeUtil.getParentOfType(file.findElementAt(startOffset), XmlTag.class);
return tag == null || !candidate.epName.endsWith(tag.getName());
}
}, scope);
}
private static void processExtensionPointCandidate(PsiElement element, List<ExtensionPointCandidate> list) {
XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
if (tag == null) return;