Quickfixes to enable unsupported and experimental features handle API level correctly and support updating the runtime library
This commit is contained in:
@@ -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 }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user