diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java new file mode 100644 index 000000000000..b02ad1adeb17 --- /dev/null +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java @@ -0,0 +1,116 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection; + +import com.intellij.analysis.JvmAnalysisBundle; +import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.UImportStatement; +import org.jetbrains.uast.UastContextKt; + +import javax.swing.*; +import java.util.List; + +public abstract class AnnotatedElementInspectionBase extends LocalInspectionTool { + public boolean myIgnoreInsideImports = true; + + + @NotNull + protected abstract List getAnnotations(); + + protected abstract void createProblem(@NotNull PsiReference reference, @NotNull ProblemsHolder holder); + + protected abstract boolean shouldProcessElement(@NotNull PsiModifierListOwner element); + + + @NotNull + @Override + public JPanel createOptionsPanel() { + return new SingleCheckboxOptionsPanel( + JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.ignore.inside.imports"), this, "myIgnoreInsideImports"); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + if (!isApplicable(holder.getFile(), holder.getProject())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + + return new PsiElementVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + if (element instanceof PsiLanguageInjectionHost) { + return; // better performance + } + + if (myIgnoreInsideImports && isInsideImport(element)) { + return; + } + + // Java constructors must be handled a bit differently (works fine with Kotlin) + PsiMethod resolvedConstructor = null; + PsiElement elementParent = element.getParent(); + if (elementParent instanceof PsiConstructorCall) { + resolvedConstructor = ((PsiConstructorCall)elementParent).resolveConstructor(); + } + + for (PsiReference reference : element.getReferences()) { + PsiModifierListOwner modifierListOwner = getModifierListOwner(reference, resolvedConstructor); + if (modifierListOwner == null || !shouldProcessElement(modifierListOwner)) { + continue; + } + + for (String annotation : getAnnotations()) { + if (modifierListOwner.hasAnnotation(annotation)) { + createProblem(reference, holder); + return; + } + } + } + } + }; + } + + private static boolean isInsideImport(@NotNull PsiElement element) { + return PsiTreeUtil.findFirstParent(element, parent -> UastContextKt.toUElement(parent, UImportStatement.class) != null) != null; + } + + @Nullable + private static PsiModifierListOwner getModifierListOwner(@NotNull PsiReference reference, @Nullable PsiMethod resolvedConstructor) { + if (resolvedConstructor != null) { + return resolvedConstructor; + } + + if (reference instanceof ResolvingHint && !((ResolvingHint)reference).canResolveTo(PsiModifierListOwner.class)) { + return null; + } + + PsiElement resolvedElement = reference.resolve(); + if (resolvedElement instanceof PsiModifierListOwner) { + return (PsiModifierListOwner)resolvedElement; + } + return null; + } + + private boolean isApplicable(@Nullable PsiFile file, @Nullable Project project) { + if (file == null || project == null) { + return false; + } + + JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); + GlobalSearchScope scope = file.getResolveScope(); + for (String annotation : getAnnotations()) { + if (javaPsiFacade.findClass(annotation, scope) != null) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java index ed39fd0dbaa5..5cc93923f526 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java @@ -2,29 +2,20 @@ package com.intellij.codeInspection; import com.intellij.analysis.JvmAnalysisBundle; -import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.codeInspection.util.SpecialAnnotationsUtil; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ig.ui.ExternalizableStringSet; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.uast.UImportStatement; -import org.jetbrains.uast.UastContextKt; import javax.swing.*; import java.awt.*; import java.util.List; -public class UnstableApiUsageInspection extends LocalInspectionTool { - public boolean myIgnoreInsideImports = true; - +public class UnstableApiUsageInspection extends AnnotatedElementInspectionBase { public final List unstableApiAnnotations = new ExternalizableStringSet( "org.jetbrains.annotations.ApiStatus.Experimental", "com.google.common.annotations.Beta", @@ -35,11 +26,27 @@ public class UnstableApiUsageInspection extends LocalInspectionTool { "org.apache.http.annotation.Beta" ); - @Nullable + @NotNull @Override - public JComponent createOptionsPanel() { - SingleCheckboxOptionsPanel checkboxPanel = new SingleCheckboxOptionsPanel( - JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.ignore.inside.imports"), this, "myIgnoreInsideImports"); + protected List getAnnotations() { + return unstableApiAnnotations; + } + + @Override + protected void createProblem(@NotNull PsiReference reference, @NotNull ProblemsHolder holder) { + String message = JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.description", getReferenceText(reference)); + holder.registerProblem(reference, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + } + + @Override + protected boolean shouldProcessElement(@NotNull PsiModifierListOwner element) { + return isLibraryElement(element); + } + + @NotNull + @Override + public JPanel createOptionsPanel() { + JPanel checkboxPanel = super.createOptionsPanel(); //TODO in add annotation window "Include non-project items" should be enabled by default JPanel annotationsListControl = SpecialAnnotationsUtil.createSpecialAnnotationsListControl( @@ -51,55 +58,7 @@ public class UnstableApiUsageInspection extends LocalInspectionTool { return panel; } - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - if (!isApplicable(holder.getFile(), holder.getProject())) { - return PsiElementVisitor.EMPTY_VISITOR; - } - - return new PsiElementVisitor() { - @Override - public void visitElement(PsiElement element) { - super.visitElement(element); - if (element instanceof PsiLanguageInjectionHost) { - return; // better performance - } - - if (myIgnoreInsideImports && isInsideImport(element)) { - return; - } - - // Java constructors must be handled a bit differently (works fine with Kotlin) - PsiMethod resolvedConstructor = null; - PsiElement elementParent = element.getParent(); - if (elementParent instanceof PsiConstructorCall) { - resolvedConstructor = ((PsiConstructorCall)elementParent).resolveConstructor(); - } - - for (PsiReference reference : element.getReferences()) { - PsiModifierListOwner modifierListOwner = getModifierListOwner(reference, resolvedConstructor); - if (modifierListOwner == null || !isLibraryElement(modifierListOwner)) { - continue; - } - - for (String annotation : unstableApiAnnotations) { - if (modifierListOwner.hasAnnotation(annotation)) { - holder.registerProblem(reference, - JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.description", getReferenceText(reference)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - return; - } - } - } - } - }; - } - - private static boolean isInsideImport(@NotNull PsiElement element) { - return PsiTreeUtil.findFirstParent(element, parent -> UastContextKt.toUElement(parent, UImportStatement.class) != null) != null; - } - + @SuppressWarnings("Duplicates") private static boolean isLibraryElement(@NotNull PsiElement element) { if (ApplicationManager.getApplication().isUnitTestMode()) { return true; @@ -127,37 +86,4 @@ public class UnstableApiUsageInspection extends LocalInspectionTool { // references are not PsiQualifiedReference for annotation attributes return StringUtil.getShortName(reference.getCanonicalText()); } - - @Nullable - private static PsiModifierListOwner getModifierListOwner(@NotNull PsiReference reference, @Nullable PsiMethod resolvedConstructor) { - if (resolvedConstructor != null) { - return resolvedConstructor; - } - - if (reference instanceof ResolvingHint && !((ResolvingHint)reference).canResolveTo(PsiModifierListOwner.class)) { - return null; - } - - PsiElement resolvedElement = reference.resolve(); - if (resolvedElement instanceof PsiModifierListOwner) { - return (PsiModifierListOwner)resolvedElement; - } - return null; - } - - private boolean isApplicable(@Nullable PsiFile file, @Nullable Project project) { - if (file == null || project == null) { - return false; - } - - JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); - GlobalSearchScope scope = file.getResolveScope(); - for (String annotation : unstableApiAnnotations) { - if (javaPsiFacade.findClass(annotation, scope) != null) { - return true; - } - } - - return false; - } }