diff --git a/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml b/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml index 4ebd95300720..b87d1c216bb4 100644 --- a/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml +++ b/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml @@ -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"/> + diff --git a/jvm/jvm-analysis-impl/resources/com/intellij/jvm/analysis/JvmAnalysisBundle.properties b/jvm/jvm-analysis-impl/resources/com/intellij/jvm/analysis/JvmAnalysisBundle.properties index ee608d01aa43..45386fb852fa 100644 --- a/jvm/jvm-analysis-impl/resources/com/intellij/jvm/analysis/JvmAnalysisBundle.properties +++ b/jvm/jvm-analysis-impl/resources/com/intellij/jvm/analysis/JvmAnalysisBundle.properties @@ -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 diff --git a/jvm/jvm-analysis-impl/resources/inspectionDescriptions/MustAlreadyBeRemovedApi.html b/jvm/jvm-analysis-impl/resources/inspectionDescriptions/MustAlreadyBeRemovedApi.html new file mode 100644 index 000000000000..952d657f5f98 --- /dev/null +++ b/jvm/jvm-analysis-impl/resources/inspectionDescriptions/MustAlreadyBeRemovedApi.html @@ -0,0 +1,10 @@ + + +Reports any declarations of classes, fields, or methods marked with @org.jetbrains.annotations.ApiStatus.ScheduledForRemoval +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 VersionComparatorUtil. + + \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/MustAlreadyBeRemovedApiInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/MustAlreadyBeRemovedApiInspection.kt new file mode 100644 index 000000000000..f605d08569d7 --- /dev/null +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/MustAlreadyBeRemovedApiInspection.kt @@ -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(textField, valueName, defaultValue) { + + override fun parseValue(text: String?): String = text ?: "" + override fun valueToString(value: String) = value + + override fun isValid(value: String): Boolean = true + + } + +} + diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/mustAlreadyBeRemovedApi/outdatedApi.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/mustAlreadyBeRemovedApi/outdatedApi.java new file mode 100644 index 000000000000..f9e6fe99abad --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/mustAlreadyBeRemovedApi/outdatedApi.java @@ -0,0 +1,33 @@ +package test; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.ScheduledForRemoval(inVersion = "2.0") +@Deprecated +class Warnings { + + @ApiStatus.ScheduledForRemoval(inVersion = "2.0") + @Deprecated + public String field; + + @ApiStatus.ScheduledForRemoval(inVersion = "2.0") + @Deprecated + public void method() { + } +} + +//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() { + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/JavaMustAlreadyBeRemovedApiInspectionTest.kt b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/JavaMustAlreadyBeRemovedApiInspectionTest.kt new file mode 100644 index 000000000000..12b2d01e4d4f --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/JavaMustAlreadyBeRemovedApiInspectionTest.kt @@ -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") + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/mustAlreadyBeRemovedApi/outdatedApi.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/mustAlreadyBeRemovedApi/outdatedApi.kt new file mode 100644 index 000000000000..aea7efaaa215 --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/mustAlreadyBeRemovedApi/outdatedApi.kt @@ -0,0 +1,33 @@ +package test + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.ScheduledForRemoval(inVersion = "2.0") +@Deprecated("") +class Warnings { + + @ApiStatus.ScheduledForRemoval(inVersion = "2.0") + @Deprecated("") + var field: String? = null + + @ApiStatus.ScheduledForRemoval(inVersion = "2.0") + @Deprecated("") + fun method() { + } +} + +//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() { + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/KotlinMustAlreadyBeRemovedApiInspectionTest.kt b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/KotlinMustAlreadyBeRemovedApiInspectionTest.kt new file mode 100644 index 000000000000..c09e117e17ce --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/KotlinMustAlreadyBeRemovedApiInspectionTest.kt @@ -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") + } +} \ No newline at end of file diff --git a/jvm/jvm-analysis-tests-api/src/com/intellij/codeInspection/tests/MustAlreadyBeRemovedApiInspectionTestBase.kt b/jvm/jvm-analysis-tests-api/src/com/intellij/codeInspection/tests/MustAlreadyBeRemovedApiInspectionTestBase.kt new file mode 100644 index 000000000000..7d6ee784882c --- /dev/null +++ b/jvm/jvm-analysis-tests-api/src/com/intellij/codeInspection/tests/MustAlreadyBeRemovedApiInspectionTestBase.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)) + } +} \ No newline at end of file