Quickfixes to enable unsupported and experimental features handle API level correctly and support updating the runtime library

This commit is contained in:
Dmitry Jemerov
2017-02-15 18:02:39 +01:00
parent ea228a2fa8
commit 4d451356eb
6 changed files with 199 additions and 34 deletions
@@ -21,6 +21,7 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ModuleRootModel
import com.intellij.util.text.VersionComparatorUtil
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.config.TargetPlatformKind
@@ -71,4 +72,10 @@ fun getDefaultLanguageLevel(
libVersion.startsWith("1.0") -> LanguageVersion.KOTLIN_1_0
else -> LanguageVersion.KOTLIN_1_1
}
}
}
fun getRuntimeLibraryVersion(module: Module): String? {
val targetPlatform = KotlinFacetSettingsProvider.getInstance(module.project).getSettings(module).versionInfo.targetPlatformKind
val versions = getRuntimeLibraryVersions(module, null, targetPlatform ?: TargetPlatformKind.Jvm[JvmTarget.JVM_1_8])
return versions.toSet().singleOrNull()
}
@@ -274,18 +274,33 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
return kotlinBlock.parent
}
fun changeLanguageVersion(module: Module, languageVersion: String, forTests: Boolean): PsiElement? {
fun changeLanguageVersion(module: Module, languageVersion: String?, apiVersion: String? = null, forTests: Boolean): PsiElement? {
return changeBuildGradle(module) { gradleFile ->
changeLanguageVersion(gradleFile, languageVersion, forTests)
var result: PsiElement? = null
if (languageVersion != null) {
result = changeLanguageVersion(gradleFile, languageVersion, forTests)
}
if (apiVersion != null) {
result = changeApiVersion(gradleFile, apiVersion, forTests)
}
result
}
}
fun changeLanguageVersion(gradleFile: GroovyFile, languageVersion: String, forTests: Boolean): PsiElement? {
val snippet = "languageVersion = \"$languageVersion\""
return changeVersion(gradleFile, languageVersion, forTests, "languageVersion")
}
fun changeApiVersion(gradleFile: GroovyFile, apiVersion: String, forTests: Boolean): PsiElement? {
return changeVersion(gradleFile, apiVersion, forTests, "apiVersion")
}
private fun changeVersion(gradleFile: GroovyFile, version: String, forTests: Boolean, versionParameter: String): PsiElement? {
val snippet = "$versionParameter = \"$version\""
val kotlinBlock = getBlockOrCreate(gradleFile, if (forTests) "compileTestKotlin" else "compileKotlin")
val experimentalBlock = getBlockOrCreate(kotlinBlock, "kotlinOptions")
addOrReplaceExpression(experimentalBlock, snippet) { stmt ->
(stmt as? GrAssignmentExpression)?.lValue?.text == "languageVersion"
(stmt as? GrAssignmentExpression)?.lValue?.text == versionParameter
}
return kotlinBlock.parent
}
@@ -23,15 +23,22 @@ import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.CoroutineSupport
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary
import org.jetbrains.kotlin.idea.versions.updateLibraries
import org.jetbrains.kotlin.psi.KtFile
sealed class ChangeCoroutineSupportFix(
@@ -43,7 +50,19 @@ sealed class ChangeCoroutineSupportFix(
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return
val runtimeUpdateRequired = coroutineSupport != CoroutineSupport.DISABLED &&
(getRuntimeLibraryVersion(module)?.startsWith("1.0") ?: false)
if (KotlinPluginUtil.isGradleModule(module)) {
if (runtimeUpdateRequired) {
Messages.showErrorDialog(project,
"Coroutines support requires version 1.1 or later of the Kotlin runtime library. " +
"Please update the version in your build script.",
super.getText())
return
}
val element = KotlinWithGradleConfigurator.changeCoroutineConfiguration(module, coroutineSupport.compilerArgument)
element?.let {
OpenFileDescriptor(project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true)
@@ -51,9 +70,24 @@ sealed class ChangeCoroutineSupportFix(
return
}
if (runtimeUpdateRequired) {
val library = findKotlinRuntimeLibrary(module)
if (library != null) {
val rc = Messages.showOkCancelDialog(project,
"Coroutines support requires version 1.1 or later of the Kotlin runtime library. " +
"Would you like to update the runtime library in your project?",
super.getText(),
Messages.getQuestionIcon())
if (rc != Messages.OK) return
updateLibraries(project, listOf(library))
}
}
val facetSettings = KotlinFacet.get(module)?.configuration?.settings ?: return
ModuleRootModificationUtil.updateModel(module) {
facetSettings.compilerInfo.coroutineSupport = coroutineSupport
facetSettings.versionInfo.apiLevel = LanguageVersion.KOTLIN_1_1
facetSettings.versionInfo.languageLevel = LanguageVersion.KOTLIN_1_1
}
}
}
@@ -62,6 +96,10 @@ sealed class ChangeCoroutineSupportFix(
override fun getText() = "${super.getText()} in the project"
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
if (coroutineSupport != CoroutineSupport.DISABLED) {
if (!checkUpdateRuntime(project, LanguageFeature.Coroutines.sinceApiVersion)) return
}
with (KotlinCommonCompilerArgumentsHolder.getInstance(project).settings) {
coroutinesEnable = coroutineSupport == CoroutineSupport.ENABLED
coroutinesWarn = coroutineSupport == CoroutineSupport.ENABLED_WITH_WARNING
@@ -69,6 +107,7 @@ sealed class ChangeCoroutineSupportFix(
}
ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true)
}
}
override fun getFamilyName() = "Enable/Disable coroutine support"
@@ -23,7 +23,10 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
@@ -31,47 +34,97 @@ import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary
import org.jetbrains.kotlin.idea.versions.updateLibraries
import org.jetbrains.kotlin.psi.KtFile
sealed class EnableUnsupportedFeatureFix(
element: PsiElement,
protected val targetVersion: LanguageVersion
protected val feature: LanguageFeature,
protected val apiVersionOnly: Boolean
) : KotlinQuickFixAction<PsiElement>(element) {
class InModule(element: PsiElement, targetVersion: LanguageVersion) : EnableUnsupportedFeatureFix(element, targetVersion) {
override fun getFamilyName() = "Increase module language level"
class InModule(element: PsiElement, feature: LanguageFeature, apiVersionOnly: Boolean) : EnableUnsupportedFeatureFix(element, feature, apiVersionOnly) {
override fun getFamilyName() = "Increase module " + if (apiVersionOnly) "API version" else "language version"
override fun getText() = "Set module language level to ${targetVersion.versionString}"
override fun getText() = if (apiVersionOnly)
"Set module API version to ${feature.sinceApiVersion.versionString}"
else
"Set module language version to ${feature.sinceVersion!!.versionString}"
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return
val targetVersion = feature.sinceVersion!!
val runtimeUpdateRequired = getRuntimeLibraryVersion(module)?.let { ApiVersion.parse(it) }?.let { runtimeVersion ->
runtimeVersion < feature.sinceApiVersion
} ?: false
val facetSettings = KotlinFacet.get(module)?.configuration?.settings ?: return
val targetApiLevel = facetSettings.versionInfo.apiLevel?.let { apiLevel ->
if (ApiVersion.createByLanguageVersion(apiLevel) < feature.sinceApiVersion)
feature.sinceApiVersion.versionString
else
null
}
if (KotlinPluginUtil.isGradleModule(module)) {
if (runtimeUpdateRequired) {
Messages.showErrorDialog(project,
"This language feature requires version ${feature.sinceApiVersion} or later of the Kotlin runtime library. " +
"Please update the version in your build script.",
"Update Language Level")
return
}
val forTests = ModuleRootManager.getInstance(module).fileIndex.isInTestSourceContent(file.virtualFile)
val element = KotlinWithGradleConfigurator.changeLanguageVersion(module, targetVersion.versionString, forTests)
val element = KotlinWithGradleConfigurator.changeLanguageVersion(module,
if (apiVersionOnly) null else targetVersion.versionString,
targetApiLevel, forTests)
element?.let {
OpenFileDescriptor(project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true)
}
return
}
val facetSettings = KotlinFacet.get(module)?.configuration?.settings ?: return
ModuleRootModificationUtil.updateModel(module) {
with(facetSettings.versionInfo) {
languageLevel = targetVersion
apiLevel = targetVersion
if (!apiVersionOnly) {
languageLevel = targetVersion
}
if (targetApiLevel != null) {
apiLevel = LanguageVersion.fromVersionString(targetApiLevel)
}
}
}
}
}
class InProject(element: PsiElement, targetVersion: LanguageVersion) : EnableUnsupportedFeatureFix(element, targetVersion) {
override fun getFamilyName() = "Increase project language level"
class InProject(element: PsiElement, feature: LanguageFeature, apiVersionOnly: Boolean)
: EnableUnsupportedFeatureFix(element, feature, apiVersionOnly)
{
override fun getFamilyName() = "Increase project " + if (apiVersionOnly) "API version" else "language version"
override fun getText() = "Set project language level to ${targetVersion.versionString}"
override fun getText() = if (apiVersionOnly)
"Set project API version to ${feature.sinceApiVersion.versionString}"
else
"Set project language version to ${feature.sinceVersion!!.versionString}"
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val targetVersion = feature.sinceVersion!!
with(KotlinCommonCompilerArgumentsHolder.getInstance(project).settings) {
languageVersion = targetVersion.versionString
apiVersion = targetVersion.versionString
val parsedApiVersion = ApiVersion.parse(apiVersion)
if (parsedApiVersion != null && feature.sinceApiVersion > parsedApiVersion) {
if (!checkUpdateRuntime(project, feature.sinceApiVersion)) return
apiVersion = feature.sinceApiVersion.versionString
}
if (!apiVersionOnly) {
languageVersion = targetVersion.versionString
}
}
ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true)
}
@@ -79,14 +132,38 @@ sealed class EnableUnsupportedFeatureFix(
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): EnableUnsupportedFeatureFix? {
val targetVersion = Errors.UNSUPPORTED_FEATURE.cast(diagnostic).a.first.sinceVersion ?: return null
val (feature, languageFeatureSettings) = Errors.UNSUPPORTED_FEATURE.cast(diagnostic).a
val sinceVersion = feature.sinceVersion ?: return null
val apiVersionOnly = sinceVersion <= languageFeatureSettings.languageVersion &&
feature.sinceApiVersion > languageFeatureSettings.apiVersion
val module = ModuleUtilCore.findModuleForPsiElement(diagnostic.psiElement) ?: return null
if (KotlinPluginUtil.isMavenModule(module)) return null
if (!KotlinPluginUtil.isGradleModule(module)) {
val facetSettings = KotlinFacet.get(module)?.configuration?.settings
if (facetSettings == null || facetSettings.useProjectSettings) return InProject(diagnostic.psiElement, targetVersion)
if (facetSettings == null || facetSettings.useProjectSettings) return InProject(diagnostic.psiElement, feature, apiVersionOnly)
}
return InModule(diagnostic.psiElement, targetVersion)
return InModule(diagnostic.psiElement, feature, apiVersionOnly)
}
}
}
fun checkUpdateRuntime(project: Project, requiredVersion: ApiVersion): Boolean {
val modulesWithOutdatedRuntime = project.allModules().filter { module ->
val parsedModuleRuntimeVersion = getRuntimeLibraryVersion(module)?.let { ApiVersion.parse(it) }
parsedModuleRuntimeVersion != null && parsedModuleRuntimeVersion < requiredVersion
}
if (modulesWithOutdatedRuntime.isNotEmpty()) {
val rc = Messages.showOkCancelDialog(project,
"This language feature requires version $requiredVersion or later of the Kotlin runtime library. " +
"Would you like to update the runtime library in your project?",
"Update runtime library",
Messages.getQuestionIcon())
if (rc != Messages.OK) return false
val librariesToUpdate = modulesWithOutdatedRuntime.mapNotNull(::findKotlinRuntimeLibrary)
updateLibraries(project, librariesToUpdate)
}
return true
}
@@ -50,6 +50,7 @@ import org.jetbrains.kotlin.idea.configuration.createConfigureKotlinNotification
import org.jetbrains.kotlin.idea.configuration.getConfiguratorByName
import org.jetbrains.kotlin.idea.framework.*
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.idea.util.runWithAlternativeResolveEnabled
import org.jetbrains.kotlin.idea.vfilefinder.JsVirtualFileFinderFactory
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
@@ -70,6 +71,20 @@ fun getLibraryRootsWithAbiIncompatibleForKotlinJs(module: Module): Collection<Bi
}
fun updateLibraries(project: Project, libraries: Collection<Library>) {
if (project.allModules().any { module -> KotlinPluginUtil.isMavenModule(module) }) {
Messages.showMessageDialog(project, "Automatic library version update for Maven projects is currently unsupported. Please update your pom.xml manually.",
"Update Kotlin Runtime Library",
Messages.getErrorIcon())
return
}
if (project.allModules().any { module -> KotlinPluginUtil.isGradleModule(module) }) {
Messages.showMessageDialog(project, "Automatic library version update for Gradle projects is currently unsupported. Please update your build.gradle manually.",
"Update Kotlin Runtime Library",
Messages.getErrorIcon())
return
}
ApplicationManager.getApplication().invokeLater {
val kJvmConfigurator = getConfiguratorByName(KotlinJavaModuleConfigurator.NAME) as KotlinJavaModuleConfigurator? ?:
error("Configurator with given name doesn't exists: " + KotlinJavaModuleConfigurator.NAME)
@@ -24,6 +24,8 @@ import com.intellij.notification.Notifications
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.libraries.Library
import com.intellij.util.PathUtil.getLocalFile
import com.intellij.util.text.VersionComparatorUtil
@@ -47,25 +49,35 @@ fun findOutdatedKotlinLibraries(project: Project): List<VersionedLibrary> {
val outdatedLibraries = arrayListOf<VersionedLibrary>()
for ((library, modules) in findAllUsedLibraries(project).entrySet()) {
val libraryVersionProperties =
getLibraryProperties(JavaRuntimePresentationProvider.getInstance(), library) ?:
getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), library) ?:
continue
val libraryVersion = libraryVersionProperties.versionString
val runtimeVersion = bundledRuntimeVersion()
val isOutdated = isRuntimeOutdated(libraryVersion, runtimeVersion)
if (isOutdated) {
outdatedLibraries.add(VersionedLibrary(library, libraryVersion, modules))
getOutdatedRuntimeLibraryVersion(library)?.let { version ->
outdatedLibraries.add(VersionedLibrary(library, version, modules))
}
}
return outdatedLibraries
}
fun getOutdatedRuntimeLibraryVersion(library: Library): String? {
val libraryVersionProperties = getKotlinLibraryVersionProperties(library) ?: return null
val libraryVersion = libraryVersionProperties.versionString
val runtimeVersion = bundledRuntimeVersion()
return if (isRuntimeOutdated(libraryVersion, runtimeVersion)) libraryVersion else null
}
private fun getKotlinLibraryVersionProperties(library: Library) =
getLibraryProperties(JavaRuntimePresentationProvider.getInstance(), library) ?:
getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), library)
fun findKotlinRuntimeLibrary(module: Module): Library? {
val orderEntries = ModuleRootManager.getInstance(module).orderEntries.filterIsInstance<LibraryOrderEntry>()
return orderEntries.asSequence()
.mapNotNull { it.library }
.firstOrNull { getKotlinLibraryVersionProperties(it) != null }
}
fun collectModulesWithOutdatedRuntime(libraries: List<VersionedLibrary>): List<Module> =
libraries.flatMap { it.usedInModules }