IDEA-216155: inspection that warns if API marked with @ScheduledForRemoval annotation must already be removed.

GitOrigin-RevId: bb9cc3c842e6110d801f718717665498a077e3a3
This commit is contained in:
Sergey Patrikeev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent 8ca5faf856
commit 9b498304a6
9 changed files with 242 additions and 0 deletions
@@ -31,6 +31,10 @@
groupBundle="com.intellij.jvm.analysis.JvmAnalysisBundle" bundle="com.intellij.jvm.analysis.JvmAnalysisBundle"
groupKey="jvm.inspections.group.name" key="jvm.inspections.missing.deprecated.annotation.on.scheduled.for.removal.api.display.name"
implementationClass="com.intellij.codeInspection.MissingDeprecatedAnnotationOnScheduledForRemovalApiInspection"/>
<localInspection language="UAST" enabledByDefault="true" level="ERROR" shortName="MustAlreadyBeRemovedApi"
groupBundle="com.intellij.jvm.analysis.JvmAnalysisBundle" bundle="com.intellij.jvm.analysis.JvmAnalysisBundle"
groupKey="jvm.inspections.group.name" key="jvm.inspections.must.already.be.removed.api.display.name"
implementationClass="com.intellij.codeInspection.MustAlreadyBeRemovedApiInspection"/>
</extensions>
<extensions defaultExtensionNs="com.intellij.codeInsight">
<blockingMethodChecker implementation="com.intellij.codeInspection.blockingCallsDetection.ThrowsTypeBlockingMethodChecker"/>
@@ -10,6 +10,9 @@ jvm.inspections.scheduled.for.removal.description.with.version=''{0}'' is schedu
jvm.inspections.missing.deprecated.annotation.on.scheduled.for.removal.api.display.name=Missing '@Deprecated' annotation on scheduled for removal API
jvm.inspections.missing.deprecated.annotation.on.scheduled.for.removal.api.description=Scheduled for removal API must also be marked with '@Deprecated' annotation
jvm.inspections.missing.deprecated.annotation.on.scheduled.for.removal.api.quick.fix=Add '@Deprecated' annotation
jvm.inspections.must.already.be.removed.api.display.name=API must already be removed
jvm.inspections.must.already.be.removed.api.earlier.version.description=API must have been removed in version {0} but the current version is {1}
jvm.inspections.must.already.be.removed.api.current.version.description=API must be removed in the current version {0}
jvm.inspections.blocking.method.problem.descriptor=Inappropriate blocking method call
jvm.inspections.blocking.method.display.name=Inappropriate thread-blocking method call
jvm.inspections.blocking.method.annotation.blocking=Blocking Annotations
@@ -0,0 +1,10 @@
<html>
<body>
Reports any declarations of classes, fields, or methods marked with <b>@org.jetbrains.annotations.ApiStatus.ScheduledForRemoval</b>
annotation that must already be removed. Specified scheduled removal version is compared with the current version that you may set below.
Versions are dot-separated strings with possibly "alpha", "beta", "eap" classifiers, like "1.0", "2.3.1", "2018.1", "7.5-snapshot",
"3.0-eap". Versions comparison is intuitive: 1.0 < 2.0, 1.0-eap < 1.0, 2.3-snapshot < 2.3 and so on.
For detailed comparison logic refer to implementation of <a
href="https://github.com/JetBrains/intellij-community/blob/master/platform/util-rt/src/com/intellij/util/text/VersionComparatorUtil.java">VersionComparatorUtil</a>.
</body>
</html>
@@ -0,0 +1,109 @@
// 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.analysis.JvmAnalysisBundle
import com.intellij.psi.PsiElementVisitor
import com.intellij.uast.UastVisitorAdapter
import com.intellij.ui.components.JBTextField
import com.intellij.ui.components.fields.valueEditors.TextFieldValueEditor
import com.intellij.util.text.VersionComparatorUtil
import com.intellij.util.ui.FormBuilder
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.UAnnotated
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.evaluateString
import org.jetbrains.uast.sourcePsiElement
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
import java.awt.BorderLayout
import javax.swing.JComponent
import javax.swing.JPanel
/**
* Reports declarations (classes, methods, fields) marked with [ApiStatus.ScheduledForRemoval] annotation
* that must already be removed. [ApiStatus.ScheduledForRemoval.inVersion] value is compared with "current" version.
*/
class MustAlreadyBeRemovedApiInspection : LocalInspectionTool() {
private companion object {
private val SCHEDULED_FOR_REMOVAL_ANNOTATION_NAME = ApiStatus.ScheduledForRemoval::class.java.canonicalName
}
var currentVersion: String = ""
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
if (currentVersion.isEmpty()) return PsiElementVisitor.EMPTY_VISITOR
return UastVisitorAdapter(MustAlreadyBeRemovedApiVisitor(holder, currentVersion), true)
}
private class MustAlreadyBeRemovedApiVisitor(
private val problemsHolder: ProblemsHolder,
private val currentVersion: String
) : AbstractUastNonRecursiveVisitor() {
override fun visitDeclaration(node: UDeclaration): Boolean {
val versionOfScheduledRemoval = getVersionOfScheduledRemoval(node)
if (versionOfScheduledRemoval != null && VersionComparatorUtil.compare(currentVersion, versionOfScheduledRemoval) >= 0) {
val message = if (currentVersion == versionOfScheduledRemoval) {
JvmAnalysisBundle.message(
"jvm.inspections.must.already.be.removed.api.current.version.description",
currentVersion
)
}
else {
JvmAnalysisBundle.message(
"jvm.inspections.must.already.be.removed.api.earlier.version.description",
versionOfScheduledRemoval,
currentVersion
)
}
val identifierPsi = node.uastAnchor.sourcePsiElement ?: return true
problemsHolder.registerProblem(identifierPsi, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
}
return true
}
private fun getVersionOfScheduledRemoval(annotated: UAnnotated): String? {
val annotation = annotated.findAnnotation(SCHEDULED_FOR_REMOVAL_ANNOTATION_NAME) ?: return null
return annotation.findDeclaredAttributeValue("inVersion")?.evaluateString()
}
}
override fun createOptionsPanel(): JComponent? {
val versionField = VersionField("Version", "")
versionField.version = currentVersion
versionField.versionEditor.addListener { version ->
currentVersion = version
}
val formBuilder = FormBuilder.createFormBuilder()
.addLabeledComponent("Current version", versionField)
val container = JPanel(BorderLayout())
container.add(formBuilder.panel, BorderLayout.NORTH)
return container
}
private class VersionField(valueName: String, defaultValue: String) : JBTextField() {
val versionEditor: VersionEditor = VersionEditor(this, valueName, defaultValue)
var version: String
get() = versionEditor.value
set(value) {
versionEditor.value = value
}
}
private class VersionEditor(textField: JBTextField, valueName: String, defaultValue: String)
: TextFieldValueEditor<String>(textField, valueName, defaultValue) {
override fun parseValue(text: String?): String = text ?: ""
override fun valueToString(value: String) = value
override fun isValid(value: String): Boolean = true
}
}
@@ -0,0 +1,33 @@
package test;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.ScheduledForRemoval(inVersion = "2.0")
@Deprecated
class <error descr="API must have been removed in version 2.0 but the current version is 3.0">Warnings</error> {
@ApiStatus.ScheduledForRemoval(inVersion = "2.0")
@Deprecated
public String <error descr="API must have been removed in version 2.0 but the current version is 3.0">field</error>;
@ApiStatus.ScheduledForRemoval(inVersion = "2.0")
@Deprecated
public void <error descr="API must have been removed in version 2.0 but the current version is 3.0">method</error>() {
}
}
//No warnings should be produced.
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "5.0")
class NoWarnings {
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "5.0")
public String field;
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "5.0")
public void method() {
}
}
@@ -0,0 +1,15 @@
package com.intellij.codeInspection.tests.java
import com.intellij.codeInspection.tests.MustAlreadyBeRemovedApiInspectionTestBase
import com.intellij.jvm.analysis.JvmAnalysisTestsUtil
import com.intellij.testFramework.TestDataPath
@TestDataPath("\$CONTENT_ROOT/testData/codeInspection/mustAlreadyBeRemovedApi")
class JavaMustAlreadyBeRemovedApiInspectionTest : MustAlreadyBeRemovedApiInspectionTestBase() {
override fun getTestDataPath() = JvmAnalysisTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + "/codeInspection/mustAlreadyBeRemovedApi"
fun `test APIs must have been removed`() {
myFixture.testHighlighting("outdatedApi.java")
}
}
@@ -0,0 +1,33 @@
package test
import org.jetbrains.annotations.ApiStatus
@ApiStatus.ScheduledForRemoval(inVersion = "2.0")
@Deprecated("")
class <error descr="API must have been removed in version 2.0 but the current version is 3.0">Warnings</error> {
@ApiStatus.ScheduledForRemoval(inVersion = "2.0")
@Deprecated("")
var <error descr="API must have been removed in version 2.0 but the current version is 3.0">field</error>: String? = null
@ApiStatus.ScheduledForRemoval(inVersion = "2.0")
@Deprecated("")
fun <error descr="API must have been removed in version 2.0 but the current version is 3.0">method</error>() {
}
}
//No warnings should be produced.
@Deprecated("")
@ApiStatus.ScheduledForRemoval(inVersion = "5.0")
class NoWarnings {
@Deprecated("")
@ApiStatus.ScheduledForRemoval(inVersion = "5.0")
var field: String? = null
@Deprecated("")
@ApiStatus.ScheduledForRemoval(inVersion = "5.0")
fun method() {
}
}
@@ -0,0 +1,15 @@
package com.intellij.codeInspection
import com.intellij.codeInspection.tests.MustAlreadyBeRemovedApiInspectionTestBase
import com.intellij.jvm.analysis.JvmAnalysisKtTestsUtil
import com.intellij.testFramework.TestDataPath
@TestDataPath("\$CONTENT_ROOT/testData/codeInspection/mustAlreadyBeRemovedApi")
class KotlinMustAlreadyBeRemovedApiInspectionTest : MustAlreadyBeRemovedApiInspectionTestBase() {
override fun getBasePath() = JvmAnalysisKtTestsUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + "/codeInspection/mustAlreadyBeRemovedApi"
fun `test APIs must have been removed`() {
myFixture.testHighlighting("outdatedApi.kt")
}
}
@@ -0,0 +1,20 @@
package com.intellij.codeInspection.tests
import com.intellij.codeInspection.MustAlreadyBeRemovedApiInspection
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
import com.intellij.util.PathUtil
import org.jetbrains.annotations.ApiStatus
abstract class MustAlreadyBeRemovedApiInspectionTestBase : JavaCodeInsightFixtureTestCase() {
override fun setUp() {
super.setUp()
val inspection = MustAlreadyBeRemovedApiInspection()
inspection.currentVersion = "3.0"
myFixture.enableInspections(inspection)
}
override fun tuneFixture(moduleBuilder: JavaModuleFixtureBuilder<*>) {
moduleBuilder.addLibrary("util", PathUtil.getJarPathForClass(ApiStatus.ScheduledForRemoval::class.java))
}
}