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