Refactoring of ScheduledForRemovalInspection\UnstableApiUsageInspection: avoid unnecessary inheritance from base class.

GitOrigin-RevId: fee3aa43418998f249a8d094bd108f6075748a93
This commit is contained in:
Sergey Patrikeev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent ea1f0bc5dd
commit fcac91fe5f
4 changed files with 189 additions and 275 deletions
@@ -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<? extends PsiAnnotation> annotations
);
void processAnnotatedMethodOverriding(
@NotNull UMethod method,
@NotNull PsiMethod overriddenMethod,
@NotNull List<? extends PsiAnnotation> annotations
);
}
@@ -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<String> 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<String> myAnnotations;
private final AnnotatedApiUsageProcessor myAnnotatedApiProcessor;
private AnnotatedApiUsageProcessorBridge(boolean ignoreInsideImports,
@NotNull List<String> 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<PsiAnnotation> annotations = AnnotationUtil.findAllAnnotations(overriddenMethod, myAnnotations, false);
if (!annotations.isEmpty()) {
myAnnotatedApiProcessor.processAnnotatedMethodOverriding(method, overriddenMethod, annotations);
}
}
private void maybeProcessAnnotatedTarget(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) {
List<PsiAnnotation> annotations = AnnotationUtil.findAllAnnotations(target, myAnnotations, false);
if (annotations.isEmpty()) {
return;
}
myAnnotatedApiProcessor.processAnnotatedTarget(sourceNode, target, annotations);
}
}
}
@@ -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<PsiAnnotation>
) {
checkScheduledForRemovalApiUsage(annotatedTarget, sourceNode, annotations, false)
}
override fun processAnnotatedMethodOverriding(
method: UMethod,
overriddenMethod: PsiMethod,
annotations: List<PsiAnnotation>
) {
checkScheduledForRemovalApiUsage(overriddenMethod, method, annotations, true)
}
private fun checkScheduledForRemovalApiUsage(
annotatedTarget: PsiModifierListOwner,
sourceNode: UElement,
annotations: List<PsiAnnotation>,
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)
}
}
}
@@ -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<String> = 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<PsiAnnotation>
) {
checkUnstableApiUsage(annotatedTarget, sourceNode, false)
}
override fun processAnnotatedMethodOverriding(
method: UMethod,
overriddenMethod: PsiMethod,
annotations: List<PsiAnnotation>
) {
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<String>
) : 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)
}
}
}