diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/inspections/gradle/GradleKotlinxCoroutinesDeprecationInspection.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/inspections/gradle/GradleKotlinxCoroutinesDeprecationInspection.kt new file mode 100644 index 00000000000..007376bbbfd --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/inspections/gradle/GradleKotlinxCoroutinesDeprecationInspection.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2010-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/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections.gradle + +import com.intellij.codeInspection.CleanupLocalInspectionTool +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.idea.configuration.getWholeModuleGroup +import org.jetbrains.kotlin.idea.inspections.gradle.GradleHeuristicHelper.PRODUCTION_DEPENDENCY_STATEMENTS +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.idea.versions.LibInfo +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.plugins.gradle.codeInspection.GradleBaseInspection +import org.jetbrains.plugins.groovy.codeInspection.BaseInspectionVisitor +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression + +private val LibInfo.gradleMarker get() = "$groupId:$name:" + +private data class DeprecatedForKotlinLibInfo( + val lib: LibInfo, + val sinceKotlinLanguageVersion: LanguageVersion, + val message: String +) + +@Suppress("SpellCheckingInspection") +private fun kotlinxCoroutinesDeprecation(name: String): org.jetbrains.kotlin.idea.inspections.gradle.DeprecatedForKotlinLibInfo { + return DeprecatedForKotlinLibInfo( + lib = LibInfo("org.jetbrains.kotlinx", name), + sinceKotlinLanguageVersion = LanguageVersion.KOTLIN_1_3, + message = "Library should be updated to be compatible with Kotlin 1.3" + ) +} + +@Suppress("SpellCheckingInspection") +private val DEPRECATED_COROUTINES_LIBRARIES_INFORMATION = listOf( + kotlinxCoroutinesDeprecation("kotlinx-coroutines"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-android"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-core"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-core-common"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-core-js"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-guava"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-io"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-javafx"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-jdk8"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-nio"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-quasar"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-reactive"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-reactor"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-rx1"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-rx2"), + kotlinxCoroutinesDeprecation("kotlinx-coroutines-swing") +) + +@Suppress("SpellCheckingInspection") +class GradleKotlinxCoroutinesDeprecationInspection : GradleBaseInspection(), CleanupLocalInspectionTool { + override fun buildVisitor(): BaseInspectionVisitor = DependencyFinder() + + private open class DependencyFinder : KotlinGradleInspectionVisitor() { + override fun visitClosure(closure: GrClosableBlock) { + super.visitClosure(closure) + + val dependenciesCall = closure.getStrictParentOfType() ?: return + if (dependenciesCall.invokedExpression.text != "dependencies") return + + val dependencyEntries = GradleHeuristicHelper.findStatementWithPrefixes(closure, PRODUCTION_DEPENDENCY_STATEMENTS) + for (dependencyStatement in dependencyEntries) { + for (outdatedInfo in DEPRECATED_COROUTINES_LIBRARIES_INFORMATION) { + val dependencyText = dependencyStatement.text + val libMarker = outdatedInfo.lib.gradleMarker + + if (!dependencyText.contains(libMarker)) continue + if (!checkKotlinVersion(dependencyStatement.containingFile, outdatedInfo.sinceKotlinLanguageVersion)) { + // Same result will be for all invocations in this file, so exit + return + } + + val reportOnElement = reportOnElement(dependencyStatement, outdatedInfo) + + registerError( + reportOnElement, outdatedInfo.message, + emptyArray(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ) + + break + } + } + } + + private fun reportOnElement(classpathEntry: GrCallExpression, deprecatedForKotlinInfo: DeprecatedForKotlinLibInfo): PsiElement { + val indexOf = classpathEntry.text.indexOf(deprecatedForKotlinInfo.lib.name) + if (indexOf < 0) return classpathEntry + + return classpathEntry.findElementAt(indexOf) ?: classpathEntry + } + + private fun checkKotlinVersion(file: PsiFile, languageVersion: LanguageVersion): Boolean { + val module = ProjectRootManager.getInstance(file.project).fileIndex.getModuleForFile(file.virtualFile) ?: return false + val moduleGroup = module.getWholeModuleGroup() + return moduleGroup.sourceRootModules.any { moduleInGroup -> + moduleInGroup.languageVersionSettings.languageVersion >= languageVersion + } + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt index 9ffa9245c03..1404089ed61 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.idea.inspections.gradle.DeprecatedGradleDependencyInspection import org.jetbrains.kotlin.idea.inspections.gradle.DifferentKotlinGradleVersionInspection import org.jetbrains.kotlin.idea.inspections.gradle.DifferentStdlibGradleVersionInspection +import org.jetbrains.kotlin.idea.inspections.gradle.GradleKotlinxCoroutinesDeprecationInspection import org.jetbrains.kotlin.idea.inspections.runInspection import org.junit.Assert import org.junit.Test @@ -343,6 +344,51 @@ class GradleInspectionTest : GradleImportingTestCase() { ) } + @Test + fun testObsoleteCoroutinesUsage() { + val localFile = createProjectSubFile( + "build.gradle", """ + group 'Again' + version '1.0-SNAPSHOT' + + buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0") + } + } + + apply plugin: 'kotlin' + + repositories { + mavenCentral() + maven { url "https://kotlin.bintray.com/kotlinx" } + } + + dependencies { + compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4' + } + + compileKotlin { + kotlinOptions.languageVersion = "1.3" + } + """ + ) + importProject() + + val tool = GradleKotlinxCoroutinesDeprecationInspection() + val problems = getInspectionResult(tool, localFile) + + Assert.assertTrue(problems.size == 1) + Assert.assertEquals( + "Library should be updated to be compatible with Kotlin 1.3", + problems.single() + ) + } + fun getInspectionResult(tool: LocalInspectionTool, file: VirtualFile): List { val resultRef = Ref>() invokeTestRunnable { diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt.172 b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt.172 index 2b5bfbb1dcd..d47e9f618e2 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt.172 +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleInspectionTest.kt.172 @@ -23,6 +23,7 @@ import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.idea.inspections.gradle.DeprecatedGradleDependencyInspection import org.jetbrains.kotlin.idea.inspections.gradle.DifferentKotlinGradleVersionInspection import org.jetbrains.kotlin.idea.inspections.gradle.DifferentStdlibGradleVersionInspection +import org.jetbrains.kotlin.idea.inspections.gradle.GradleKotlinxCoroutinesDeprecationInspection import org.jetbrains.kotlin.idea.inspections.runInspection import org.junit.Assert import org.junit.Test @@ -343,6 +344,51 @@ class GradleInspectionTest : GradleImportingTestCase() { ) } + @Test + fun testObsoleteCoroutinesUsage() { + val localFile = createProjectSubFile( + "build.gradle", """ + group 'Again' + version '1.0-SNAPSHOT' + + buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0") + } + } + + apply plugin: 'kotlin' + + repositories { + mavenCentral() + maven { url "https://kotlin.bintray.com/kotlinx" } + } + + dependencies { + compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4' + } + + compileKotlin { + kotlinOptions.languageVersion = "1.3" + } + """ + ) + importProject() + + val tool = GradleKotlinxCoroutinesDeprecationInspection() + val problems = getInspectionResult(tool, localFile) + + Assert.assertTrue(problems.size == 1) + Assert.assertEquals( + "Library should be updated to be compatible with Kotlin 1.3", + problems.single() + ) + } + fun getInspectionResult(tool: LocalInspectionTool, file: VirtualFile): List { val resultRef = Ref>() invokeTestRunnable { diff --git a/idea/resources/inspectionDescriptions/GradleKotlinxCoroutinesDeprecation.html b/idea/resources/inspectionDescriptions/GradleKotlinxCoroutinesDeprecation.html new file mode 100644 index 00000000000..1e318f2706d --- /dev/null +++ b/idea/resources/inspectionDescriptions/GradleKotlinxCoroutinesDeprecation.html @@ -0,0 +1,6 @@ + + +This inspection reports kotlinx.coroutines libraries dependencies in Gradle that should be updated in order +to be compatible with Kotlin 1.3+. + + \ No newline at end of file diff --git a/idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutinesInspection.html b/idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutines.html similarity index 100% rename from idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutinesInspection.html rename to idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutines.html diff --git a/idea/src/META-INF/gradle-java.xml.182 b/idea/src/META-INF/gradle-java.xml.182 index 8b53827b6af..1a8d0a6a72c 100644 --- a/idea/src/META-INF/gradle-java.xml.182 +++ b/idea/src/META-INF/gradle-java.xml.182 @@ -48,6 +48,16 @@ hasStaticDescription="true" level="WARNING"/> + + diff --git a/idea/src/META-INF/gradle.xml b/idea/src/META-INF/gradle.xml index 255ff015624..4a607172849 100644 --- a/idea/src/META-INF/gradle.xml +++ b/idea/src/META-INF/gradle.xml @@ -56,6 +56,16 @@ hasStaticDescription="true" level="WARNING"/> + + diff --git a/idea/src/META-INF/gradle.xml.172 b/idea/src/META-INF/gradle.xml.172 index d6ea42e8826..0bc1d7b7045 100644 --- a/idea/src/META-INF/gradle.xml.172 +++ b/idea/src/META-INF/gradle.xml.172 @@ -57,6 +57,16 @@ hasStaticDescription="true" level="WARNING"/> + +