diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedApiUsageProcessor.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedApiUsageProcessor.java deleted file mode 100644 index 4492de4d1ecf..000000000000 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedApiUsageProcessor.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2000-2019 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.psi.PsiAnnotation; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifierListOwner; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UMethod; - -import java.util.List; - -/** - * Processor of usages of APIs marked with specified annotations, which are detected by {@link AnnotatedElementInspectionBase}. - */ -public interface AnnotatedApiUsageProcessor { - void processAnnotatedTarget( - @NotNull UElement sourceNode, - @NotNull PsiModifierListOwner annotatedTarget, - @NotNull List annotations - ); - - void processAnnotatedMethodOverriding( - @NotNull UMethod method, - @NotNull PsiMethod overriddenMethod, - @NotNull List annotations - ); -} diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java deleted file mode 100644 index d5536e71efd5..000000000000 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java +++ /dev/null @@ -1,140 +0,0 @@ -// 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.codeInsight.AnnotationUtil; -import com.intellij.codeInspection.apiUsage.ApiUsageProcessor; -import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor; -import com.intellij.codeInspection.deprecation.DeprecationInspectionBase; -import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiUtilCore; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; - -import javax.swing.*; -import java.util.List; - -/** - * This class can be extended by inspections that should report usage of elements annotated with some particular annotation(s). - */ -public abstract class AnnotatedElementInspectionBase extends LocalInspectionTool { - public boolean myIgnoreInsideImports = true; - - - @NotNull - protected abstract List getAnnotations(); - - @NotNull - @Override - public JPanel createOptionsPanel() { - return new SingleCheckboxOptionsPanel( - JvmAnalysisBundle.message("jvm.inspections.api.usage.ignore.inside.imports"), this, "myIgnoreInsideImports"); - } - - @NotNull - protected abstract AnnotatedApiUsageProcessor buildAnnotatedApiUsageProcessor(@NotNull ProblemsHolder holder); - - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - if (!isApplicable(holder.getFile(), holder.getProject())) { - return PsiElementVisitor.EMPTY_VISITOR; - } - - AnnotatedApiUsageProcessor annotatedApiProcessor = buildAnnotatedApiUsageProcessor(holder); - AnnotatedApiUsageProcessorBridge processorBridge = new AnnotatedApiUsageProcessorBridge( - myIgnoreInsideImports, getAnnotations(), annotatedApiProcessor - ); - return ApiUsageUastVisitor.createPsiElementVisitor(processorBridge); - } - - 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; - } - - protected static String getPresentableText(@NotNull PsiElement psiElement) { - return DeprecationInspectionBase.getPresentableName(psiElement); - } - - protected static boolean isLibraryElement(@NotNull PsiElement element) { - if (ApplicationManager.getApplication().isUnitTestMode()) { - return true; - } - VirtualFile containingVirtualFile = PsiUtilCore.getVirtualFile(element); - return containingVirtualFile != null && ProjectFileIndex.getInstance(element.getProject()).isInLibraryClasses(containingVirtualFile); - } - - private static final class AnnotatedApiUsageProcessorBridge implements ApiUsageProcessor { - private final boolean myIgnoreInsideImports; - private final List myAnnotations; - private final AnnotatedApiUsageProcessor myAnnotatedApiProcessor; - - private AnnotatedApiUsageProcessorBridge(boolean ignoreInsideImports, - @NotNull List annotations, - @NotNull AnnotatedApiUsageProcessor annotatedApiProcessor) { - myIgnoreInsideImports = ignoreInsideImports; - myAnnotations = annotations; - myAnnotatedApiProcessor = annotatedApiProcessor; - } - - @Override - public void processImportReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { - if (!myIgnoreInsideImports) { - maybeProcessAnnotatedTarget(sourceNode, target); - } - } - - @Override - public void processReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target, @Nullable UExpression qualifier) { - maybeProcessAnnotatedTarget(sourceNode, target); - } - - @Override - public void processConstructorInvocation(@NotNull UElement sourceNode, - @NotNull PsiClass instantiatedClass, - @Nullable PsiMethod constructor, - @Nullable UClass subclassDeclaration) { - if (constructor != null) { - maybeProcessAnnotatedTarget(sourceNode, constructor); - } - } - - @Override - public void processMethodOverriding(@NotNull UMethod method, @NotNull PsiMethod overriddenMethod) { - List annotations = AnnotationUtil.findAllAnnotations(overriddenMethod, myAnnotations, false); - if (!annotations.isEmpty()) { - myAnnotatedApiProcessor.processAnnotatedMethodOverriding(method, overriddenMethod, annotations); - } - } - - private void maybeProcessAnnotatedTarget(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { - List annotations = AnnotationUtil.findAllAnnotations(target, myAnnotations, false); - if (annotations.isEmpty()) { - return; - } - myAnnotatedApiProcessor.processAnnotatedTarget(sourceNode, target, annotations); - } - } -} \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.kt index 2314a64ac1fb..9a53fac1e3d5 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.kt @@ -3,74 +3,114 @@ package com.intellij.codeInspection import com.intellij.analysis.JvmAnalysisBundle import com.intellij.codeInsight.AnnotationUtil -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiMethod -import com.intellij.psi.PsiModifierListOwner +import com.intellij.codeInspection.apiUsage.ApiUsageProcessor +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor +import com.intellij.codeInspection.deprecation.DeprecationInspectionBase +import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.psi.* +import com.intellij.psi.util.PsiUtilCore import org.jetbrains.annotations.ApiStatus -import org.jetbrains.uast.UDeclaration -import org.jetbrains.uast.UElement -import org.jetbrains.uast.UMethod -import org.jetbrains.uast.sourcePsiElement +import org.jetbrains.uast.* -//TODO quickfix like in deprecation inspection? -class ScheduledForRemovalInspection : AnnotatedElementInspectionBase() { +class ScheduledForRemovalInspection : LocalInspectionTool() { + + @JvmField + var myIgnoreInsideImports: Boolean = true + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = + ApiUsageUastVisitor.createPsiElementVisitor( + ScheduledForRemovalApiUsageProcessor(holder, myIgnoreInsideImports) + ) + + override fun createOptionsPanel() = SingleCheckboxOptionsPanel( + JvmAnalysisBundle.message("jvm.inspections.api.usage.ignore.inside.imports"), this, "myIgnoreInsideImports" + ) +} + +private class ScheduledForRemovalApiUsageProcessor( + private val problemsHolder: ProblemsHolder, + private val ignoreInsideImports: Boolean +) : ApiUsageProcessor { private companion object { private val ANNOTATION_NAME = ApiStatus.ScheduledForRemoval::class.java.canonicalName + + fun findScheduledForRemovalAnnotation(psiModifierListOwner: PsiModifierListOwner): PsiAnnotation? { + val ownAnnotation = AnnotationUtil.findAnnotation(psiModifierListOwner, listOf(ANNOTATION_NAME), false) + if (ownAnnotation != null) { + return ownAnnotation + } + return null + } + + fun isLibraryElement(element: PsiElement): Boolean { + if (ApplicationManager.getApplication().isUnitTestMode) { + return true + } + val containingVirtualFile = PsiUtilCore.getVirtualFile(element) + return containingVirtualFile != null && ProjectFileIndex.getInstance(element.project).isInLibraryClasses(containingVirtualFile) + } } - override fun getAnnotations() = listOf(ANNOTATION_NAME) - - override fun buildAnnotatedApiUsageProcessor(holder: ProblemsHolder): AnnotatedApiUsageProcessor = - object : AnnotatedApiUsageProcessor { - override fun processAnnotatedTarget( - sourceNode: UElement, - annotatedTarget: PsiModifierListOwner, - annotations: List - ) { - checkScheduledForRemovalApiUsage(annotatedTarget, sourceNode, annotations, false) - } - - override fun processAnnotatedMethodOverriding( - method: UMethod, - overriddenMethod: PsiMethod, - annotations: List - ) { - checkScheduledForRemovalApiUsage(overriddenMethod, method, annotations, true) - } - - private fun checkScheduledForRemovalApiUsage( - annotatedTarget: PsiModifierListOwner, - sourceNode: UElement, - annotations: List, - isMethodOverriding: Boolean - ) { - if (!isLibraryElement(annotatedTarget)) { - return - } - val elementToHighlight = (sourceNode as? UDeclaration)?.uastAnchor.sourcePsiElement ?: sourceNode.sourcePsi - val scheduledForRemoval = annotations.find { psiAnnotation -> psiAnnotation.hasQualifiedName(ANNOTATION_NAME) } - if (elementToHighlight != null && scheduledForRemoval != null) { - val inVersion = AnnotationUtil.getDeclaredStringAttributeValue(scheduledForRemoval, "inVersion") - val targetName = getPresentableText(annotatedTarget) - val isEmptyVersion = inVersion == null || inVersion.isEmpty() - val message: String = when { - isEmptyVersion && isMethodOverriding -> JvmAnalysisBundle.message( - "jvm.inspections.scheduled.for.removal.method.overridden.no.version.description", targetName - ) - - !isEmptyVersion && isMethodOverriding -> JvmAnalysisBundle.message( - "jvm.inspections.scheduled.for.removal.method.overridden.with.version.description", targetName, inVersion - ) - - !isEmptyVersion && !isMethodOverriding -> JvmAnalysisBundle.message( - "jvm.inspections.scheduled.for.removal.description.with.version", targetName, inVersion - ) - - else -> JvmAnalysisBundle.message("jvm.inspections.scheduled.for.removal.description.no.version", targetName) - } - holder.registerProblem(elementToHighlight, message, ProblemHighlightType.LIKE_MARKED_FOR_REMOVAL) - } - } + override fun processImportReference(sourceNode: UElement, target: PsiModifierListOwner) { + if (!ignoreInsideImports) { + checkScheduledForRemovalApiUsage(target, sourceNode, false) } -} + } + + override fun processReference(sourceNode: UElement, target: PsiModifierListOwner, qualifier: UExpression?) { + checkScheduledForRemovalApiUsage(target, sourceNode, false) + } + + override fun processConstructorInvocation(sourceNode: UElement, + instantiatedClass: PsiClass, + constructor: PsiMethod?, + subclassDeclaration: UClass?) { + if (constructor != null) { + checkScheduledForRemovalApiUsage(constructor, sourceNode, false) + } + } + + override fun processMethodOverriding(method: UMethod, overriddenMethod: PsiMethod) { + checkScheduledForRemovalApiUsage(overriddenMethod, method, true) + } + + fun checkScheduledForRemovalApiUsage(target: PsiModifierListOwner, sourceNode: UElement, isMethodOverriding: Boolean) { + if (!isLibraryElement(target)) { + return + } + val scheduledForRemovalAnnotation = findScheduledForRemovalAnnotation(target) ?: return + val elementToHighlight = (sourceNode as? UDeclaration)?.uastAnchor.sourcePsiElement ?: sourceNode.sourcePsi + if (elementToHighlight != null) { + val message = buildMessage(scheduledForRemovalAnnotation, target, isMethodOverriding) + problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.LIKE_MARKED_FOR_REMOVAL) + } + } + + private fun buildMessage( + scheduledForRemovalAnnotation: PsiAnnotation, + target: PsiModifierListOwner, + isMethodOverriding: Boolean + ): String { + val inVersion = AnnotationUtil.getDeclaredStringAttributeValue(scheduledForRemovalAnnotation, "inVersion") + val targetName = DeprecationInspectionBase.getPresentableName(target) + val isEmptyVersion = inVersion == null || inVersion.isEmpty() + return when { + isEmptyVersion && isMethodOverriding -> JvmAnalysisBundle.message( + "jvm.inspections.scheduled.for.removal.method.overridden.no.version.description", targetName + ) + + !isEmptyVersion && isMethodOverriding -> JvmAnalysisBundle.message( + "jvm.inspections.scheduled.for.removal.method.overridden.with.version.description", targetName, inVersion + ) + + !isEmptyVersion && !isMethodOverriding -> JvmAnalysisBundle.message( + "jvm.inspections.scheduled.for.removal.description.with.version", targetName, inVersion + ) + + else -> JvmAnalysisBundle.message("jvm.inspections.scheduled.for.removal.description.no.version", targetName) + } + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.kt index 7005b8b2af23..0653b7c3d760 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.kt @@ -2,21 +2,26 @@ package com.intellij.codeInspection import com.intellij.analysis.JvmAnalysisBundle +import com.intellij.codeInsight.AnnotationUtil +import com.intellij.codeInspection.apiUsage.ApiUsageProcessor +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor import com.intellij.codeInspection.deprecation.DeprecationInspectionBase.getPresentableName +import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel import com.intellij.codeInspection.util.SpecialAnnotationsUtil -import com.intellij.psi.PsiAnnotation +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.util.PsiUtilCore import com.intellij.util.ArrayUtilRt import com.siyeh.ig.ui.ExternalizableStringSet -import org.jetbrains.uast.UDeclaration -import org.jetbrains.uast.UElement -import org.jetbrains.uast.UMethod -import org.jetbrains.uast.sourcePsiElement +import org.jetbrains.uast.* import java.awt.BorderLayout import javax.swing.JPanel -class UnstableApiUsageInspection : AnnotatedElementInspectionBase() { +class UnstableApiUsageInspection : LocalInspectionTool() { companion object { val DEFAULT_UNSTABLE_API_ANNOTATIONS: List = listOf( @@ -37,50 +42,23 @@ class UnstableApiUsageInspection : AnnotatedElementInspectionBase() { *ArrayUtilRt.toStringArray(DEFAULT_UNSTABLE_API_ANNOTATIONS) ) - override fun getAnnotations() = unstableApiAnnotations + @JvmField + var myIgnoreInsideImports: Boolean = true - override fun buildAnnotatedApiUsageProcessor(holder: ProblemsHolder) = - object : AnnotatedApiUsageProcessor { - override fun processAnnotatedTarget( - sourceNode: UElement, - annotatedTarget: PsiModifierListOwner, - annotations: List - ) { - checkUnstableApiUsage(annotatedTarget, sourceNode, false) - } - - override fun processAnnotatedMethodOverriding( - method: UMethod, - overriddenMethod: PsiMethod, - annotations: List - ) { - checkUnstableApiUsage(overriddenMethod, method, true) - } - - private fun checkUnstableApiUsage(annotatedTarget: PsiModifierListOwner, sourceNode: UElement, isMethodOverriding: Boolean) { - if (!isLibraryElement(annotatedTarget)) { - return - } - val targetName = getPresentableName(annotatedTarget) - val message = if (isMethodOverriding) { - JvmAnalysisBundle.message("jvm.inspections.unstable.method.overridden.description", targetName) - } - else { - JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.description", targetName) - } - val elementToHighlight = (sourceNode as? UDeclaration)?.uastAnchor.sourcePsiElement ?: sourceNode.sourcePsi - if (elementToHighlight != null) { - holder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING) - } - } - } + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = + ApiUsageUastVisitor.createPsiElementVisitor( + UnstableApiUsageProcessor(holder, myIgnoreInsideImports, unstableApiAnnotations.toList()) + ) override fun createOptionsPanel(): JPanel { - val checkboxPanel = super.createOptionsPanel() + val checkboxPanel = SingleCheckboxOptionsPanel( + JvmAnalysisBundle.message("jvm.inspections.api.usage.ignore.inside.imports"), this, "myIgnoreInsideImports" + ) //TODO in add annotation window "Include non-project items" should be enabled by default val annotationsListControl = SpecialAnnotationsUtil.createSpecialAnnotationsListControl( - unstableApiAnnotations, JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.annotations.list")) + unstableApiAnnotations, JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.annotations.list") + ) val panel = JPanel(BorderLayout(2, 2)) panel.add(checkboxPanel, BorderLayout.NORTH) @@ -88,3 +66,67 @@ class UnstableApiUsageInspection : AnnotatedElementInspectionBase() { return panel } } + +private class UnstableApiUsageProcessor( + private val problemsHolder: ProblemsHolder, + private val ignoreInsideImports: Boolean, + private val annotations: List +) : ApiUsageProcessor { + + private companion object { + fun isLibraryElement(element: PsiElement): Boolean { + if (ApplicationManager.getApplication().isUnitTestMode) { + return true + } + val containingVirtualFile = PsiUtilCore.getVirtualFile(element) + return containingVirtualFile != null && ProjectFileIndex.getInstance(element.project).isInLibraryClasses(containingVirtualFile) + } + } + + override fun processImportReference(sourceNode: UElement, target: PsiModifierListOwner) { + if (!ignoreInsideImports) { + checkUnstableApiUsage(target, sourceNode, false) + } + } + + override fun processReference(sourceNode: UElement, target: PsiModifierListOwner, qualifier: UExpression?) { + checkUnstableApiUsage(target, sourceNode, false) + } + + override fun processConstructorInvocation( + sourceNode: UElement, + instantiatedClass: PsiClass, + constructor: PsiMethod?, + subclassDeclaration: UClass? + ) { + if (constructor != null) { + checkUnstableApiUsage(constructor, sourceNode, false) + } + } + + override fun processMethodOverriding(method: UMethod, overriddenMethod: PsiMethod) { + checkUnstableApiUsage(overriddenMethod, method, true) + } + + private fun checkUnstableApiUsage(target: PsiModifierListOwner, sourceNode: UElement, isMethodOverriding: Boolean) { + if (!isLibraryElement(target)) { + return + } + val annotations = AnnotationUtil.findAllAnnotations(target, annotations, false) + if (annotations.isEmpty()) { + return + } + val targetName = getPresentableName(target) + val message = if (isMethodOverriding) { + JvmAnalysisBundle.message("jvm.inspections.unstable.method.overridden.description", targetName) + } + else { + JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.description", targetName) + } + val elementToHighlight = (sourceNode as? UDeclaration)?.uastAnchor.sourcePsiElement ?: sourceNode.sourcePsi + if (elementToHighlight != null) { + problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + } + } + +}