Improve kotlin jvm run config producers for 181 and as32 branches

Drop KotlinJUnitRunConfigurationProducer, KotlinRunConfigurationProducer
    bunch files
Extract differences in behaviour to 'jvmMppUtils.kt'
Improve 'isConfigurationFromContext' checks for 181 and as32
    (this prevents duplicate configurations being created)
KotlinRunConfigurationProducer chooses corresponding jvm module
    for common code

 #KT-26886 Fixed
This commit is contained in:
Pavel V. Talanov
2018-09-20 19:26:45 +02:00
parent f34f306d90
commit 71d640f3f8
7 changed files with 65 additions and 353 deletions
@@ -29,8 +29,6 @@ import com.intellij.openapi.util.component2
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.platform.impl.isJvm
import org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer
import org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer
import org.jetbrains.plugins.gradle.util.GradleConstants
@@ -74,8 +72,7 @@ class KotlinTestClassGradleConfigurationProducer : TestClassGradleConfigurationP
if (!IS_TEST_FRAMEWORK_PLUGIN_ENABLED) return false
val contextLocation = context.location ?: return false
val module = context.module ?: return false
if (!module.platform.isJvm) return false
val module = context.module?.asJvmModule() ?: return false
if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) {
return false
@@ -104,8 +101,7 @@ class KotlinTestClassGradleConfigurationProducer : TestClassGradleConfigurationP
if (!IS_TEST_FRAMEWORK_PLUGIN_ENABLED) return false
val leaf = context.location?.psiElement ?: return false
val module = context.module ?: return false
if (!module.platform.isJvm) return false
val module = context.module?.asJvmModule() ?: return false
if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) {
return false
@@ -141,8 +137,7 @@ class KotlinTestMethodGradleConfigurationProducer : TestMethodGradleConfiguratio
if (!IS_TEST_FRAMEWORK_PLUGIN_ENABLED) return false
val contextLocation = context.location ?: return false
val module = context.module ?: return false
if (!module.platform.isJvm) return false
context.module?.asJvmModule() ?: return false
if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) {
return false
@@ -168,8 +163,7 @@ class KotlinTestMethodGradleConfigurationProducer : TestMethodGradleConfiguratio
}
val contextLocation = context.location ?: return false
val module = context.module ?: return false
if (!module.platform.isJvm) return false
val module = context.module?.asJvmModule() ?: return false
val psiMethod = getTestMethod(contextLocation.psiElement) ?: return false
@@ -11,21 +11,21 @@ import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.execution.configurations.ModuleBasedConfiguration
import com.intellij.execution.junit.*
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfiguration>(JUnitConfigurationType.getInstance()) {
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
@@ -62,7 +62,7 @@ class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfig
val template = RunManager.getInstance(configuration.project).getConfigurationTemplate(configurationFactory)
val predefinedModule = (template.configuration as ModuleBasedConfiguration<*>).configurationModule.module
val configurationModule = configuration.configurationModule.module
return configurationModule == context.location?.module || configurationModule == predefinedModule
return configurationModule == context.location?.module?.asJvmModule() || configurationModule == predefinedModule
}
override fun setupConfigurationFromContext(
@@ -74,6 +74,7 @@ class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfig
val location = context.location ?: return false
val leaf = location.psiElement
val module = context.module?.asJvmModule() ?: return false
if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) {
return false
@@ -83,36 +84,38 @@ class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfig
return false
}
val module = location.module ?: return false
val targetPlatform = TargetPlatformDetector.getPlatform(module)
if (targetPlatform != JvmPlatform) {
return false
}
configuration.setModule(module)
val methodLocation = getTestMethodLocation(leaf)
if (methodLocation != null) {
val originalModule = configuration.configurationModule.module
configuration.beMethodConfiguration(methodLocation)
configuration.restoreOriginalModule(originalModule)
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
configuration.setSdkAndModule(module)
return true
}
val testClass = getTestClass(leaf)
if (testClass != null) {
val originalModule = configuration.configurationModule.module
configuration.beClassConfiguration(testClass)
configuration.restoreOriginalModule(originalModule)
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
configuration.setSdkAndModule(module)
return true
}
return false
}
private fun JUnitConfiguration.setSdkAndModule(module: Module) {
if (configurationModule.module == module) {
return
}
setModule(module)
val sdk = ModuleRootManager.getInstance(module).sdk
if (sdk != null) {
isAlternativeJrePathEnabled = true
alternativeJrePath = sdk.homePath
}
}
override fun onFirstRun(fromContext: ConfigurationFromContext, context: ConfigurationContext, performRunnable: Runnable) {
val leaf = fromContext.sourceElement
getTestClass(leaf)?.let { testClass ->
@@ -1,180 +0,0 @@
/*
* 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.run
import com.intellij.execution.*
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.execution.configurations.ModuleBasedConfiguration
import com.intellij.execution.junit.*
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfiguration>(JUnitConfigurationType.getInstance()) {
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return other.isProducedBy(JUnitConfigurationProducer::class.java) || other.isProducedBy(PatternConfigurationProducer::class.java)
}
override fun isConfigurationFromContext(
configuration: JUnitConfiguration,
context: ConfigurationContext
): Boolean {
if (RunConfigurationProducer.getInstance(PatternConfigurationProducer::class.java).isMultipleElementsSelected(context)) {
return false
}
val leaf = context.location?.psiElement ?: return false
val methodLocation = getTestMethodLocation(leaf)
val testClass = getTestClass(leaf)
val testObject = configuration.testObject
if (!testObject.isConfiguredByElement(configuration, testClass, methodLocation?.psiElement, null, null)) {
return false
}
return settingsMatchTemplate(configuration, context)
}
// copied from JUnitConfigurationProducer in IDEA
private fun settingsMatchTemplate(configuration: JUnitConfiguration, context: ConfigurationContext): Boolean {
val predefinedConfiguration = context.getOriginalConfiguration(JUnitConfigurationType.getInstance())
val vmParameters = (predefinedConfiguration as? CommonJavaRunConfigurationParameters)?.vmParameters
if (vmParameters != null && configuration.vmParameters != vmParameters) return false
val template = RunManager.getInstance(configuration.project).getConfigurationTemplate(configurationFactory)
val predefinedModule = (template.configuration as ModuleBasedConfiguration<*>).configurationModule.module
val configurationModule = configuration.configurationModule.module
return configurationModule == context.location?.module || configurationModule == predefinedModule
}
override fun setupConfigurationFromContext(
configuration: JUnitConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>
): Boolean {
if (DumbService.getInstance(context.project).isDumb) return false
val location = context.location ?: return false
val leaf = location.psiElement
if (!ProjectRootsUtil.isInProjectOrLibSource(leaf)) {
return false
}
if (leaf.containingFile !is KtFile) {
return false
}
val ktFile = leaf.containingFile as KtFile
val targetPlatform = TargetPlatformDetector.getPlatform(ktFile)
if (targetPlatform != JvmPlatform && targetPlatform != TargetPlatform.Common) {
return false
}
val methodLocation = getTestMethodLocation(leaf)
if (methodLocation != null) {
val originalModule = configuration.configurationModule.module
configuration.beMethodConfiguration(methodLocation)
configuration.restoreOriginalModule(originalModule)
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
fixJdkForCommonModule(ktFile, configuration)
return true
}
val testClass = getTestClass(leaf)
if (testClass != null) {
val originalModule = configuration.configurationModule.module
configuration.beClassConfiguration(testClass)
configuration.restoreOriginalModule(originalModule)
JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, location)
fixJdkForCommonModule(ktFile, configuration)
return true
}
return false
}
private fun fixJdkForCommonModule(ktFile: KtFile, configuration: JUnitConfiguration) {
val implModule = ktFile.module?.findJvmImplementationModule() ?: return
val sdk = ModuleRootManager.getInstance(implModule).sdk
if (sdk != null) {
configuration.setModule(implModule)
configuration.isAlternativeJrePathEnabled = true
configuration.alternativeJrePath = sdk.homePath
}
}
override fun onFirstRun(fromContext: ConfigurationFromContext, context: ConfigurationContext, performRunnable: Runnable) {
val leaf = fromContext.sourceElement
getTestClass(leaf)?.let { testClass ->
val fromContextSubstitute = object : ConfigurationFromContext() {
override fun getConfigurationSettings() = fromContext.configurationSettings
override fun setConfigurationSettings(configurationSettings: RunnerAndConfigurationSettings) {
fromContext.configurationSettings = configurationSettings
}
override fun getSourceElement() = testClass
}
// TODO: use TestClassConfigurationProducer when constructor becomes public
return object : AbstractTestClassConfigurationProducer(JUnitConfigurationType.getInstance()){}
.onFirstRun(fromContextSubstitute, context, performRunnable)
}
super.onFirstRun(fromContext, context, performRunnable)
}
companion object {
fun getTestClass(leaf: PsiElement): PsiClass? {
val containingFile = leaf.containingFile as? KtFile ?: return null
var ktClass = leaf.getParentOfType<KtClass>(false)
if (!ktClass.isJUnitTestClass()) {
ktClass = getTestClassInFile(containingFile)
}
return ktClass?.toLightClass()
}
fun getTestMethodLocation(leaf: PsiElement): Location<PsiMethod>? {
val function = leaf.getParentOfType<KtNamedFunction>(false) ?: return null
val owner = PsiTreeUtil.getParentOfType(function, KtFunction::class.java, KtClass::class.java)
if (owner is KtClass) {
val delegate = owner.toLightClass() ?: return null
val method = delegate.methods.firstOrNull() { it.navigationElement == function } ?: return null
val methodLocation = PsiLocation.fromPsiElement(method)
if (JUnitUtil.isTestMethod(methodLocation, false)) {
return methodLocation
}
}
return null
}
private fun KtClass?.isJUnitTestClass() =
this?.toLightClass()?.let { JUnitUtil.isTestClass(it, false, true) } ?: false
private fun getTestClassInFile(ktFile: KtFile) =
ktFile.declarations.filterIsInstance<KtClass>().singleOrNull { it.isJUnitTestClass() }
}
}
@@ -28,12 +28,10 @@ import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinRunConfigurationProducer : RunConfigurationProducer<KotlinRunConfiguration>(KotlinRunConfigurationType.getInstance()) {
@@ -42,7 +40,7 @@ class KotlinRunConfigurationProducer : RunConfigurationProducer<KotlinRunConfigu
context: ConfigurationContext,
sourceElement: Ref<PsiElement>): Boolean {
val location = context.location ?: return false
val module = location.module ?: return false
val module = location.module?.asJvmModule() ?: return false
val container = getEntryPointContainer(location) ?: return false
val startClassFQName = getStartClassFqName(container) ?: return false
@@ -57,14 +55,7 @@ class KotlinRunConfigurationProducer : RunConfigurationProducer<KotlinRunConfigu
if (location == null) return null
if (DumbService.getInstance(location.project).isDumb) return null
val module = location.module ?: return null
if (TargetPlatformDetector.getPlatform(module) !is JvmPlatform) {
return null
}
val locationElement = location.psiElement
return getEntryPointContainer(locationElement)
return getEntryPointContainer(location.psiElement)
}
override fun isConfigurationFromContext(configuration: KotlinRunConfiguration, context: ConfigurationContext): Boolean {
@@ -72,7 +63,7 @@ class KotlinRunConfigurationProducer : RunConfigurationProducer<KotlinRunConfigu
val startClassFQName = getStartClassFqName(entryPointContainer) ?: return false
return configuration.runClass == startClassFQName &&
context.module == configuration.configurationModule.module
context.module?.asJvmModule() == configuration.configurationModule.module
}
companion object {
@@ -1,135 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.run
import com.intellij.execution.Location
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import com.intellij.psi.util.ClassUtil
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.idea.caches.project.implementingModules
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.platform.impl.isCommon
import org.jetbrains.kotlin.platform.impl.isJvm
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinRunConfigurationProducer : RunConfigurationProducer<KotlinRunConfiguration>(KotlinRunConfigurationType.getInstance()) {
override fun setupConfigurationFromContext(configuration: KotlinRunConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>): Boolean {
val location = context.location ?: return false
val module = location.module ?: return false
val container = getEntryPointContainer(location)
val startClassFQName = getStartClassFqName(container) ?: return false
configuration.setModule(module)
configuration.runClass = startClassFQName
configuration.setGeneratedName()
return true
}
private fun getEntryPointContainer(location: Location<*>?): KtDeclarationContainer? {
if (location == null) return null
if (DumbService.getInstance(location.project).isDumb) return null
val module = location.module ?: return null
if (TargetPlatformDetector.getPlatform(module) !is JvmPlatform && module.findJvmImplementationModule() == null) {
return null
}
val locationElement = location.psiElement
return getEntryPointContainer(locationElement)
}
override fun isConfigurationFromContext(configuration: KotlinRunConfiguration, context: ConfigurationContext): Boolean {
val startClassFQName = getStartClassFqName(getEntryPointContainer(context.location)) ?: return false
return configuration.runClass == startClassFQName &&
context.module == configuration.configurationModule.module
}
companion object {
fun getEntryPointContainer(locationElement: PsiElement): KtDeclarationContainer? {
val psiFile = locationElement.containingFile
if (!(psiFile is KtFile && ProjectRootsUtil.isInProjectOrLibSource(psiFile))) return null
val mainFunctionDetector =
MainFunctionDetector(psiFile.languageVersionSettings) { it.resolveToDescriptorIfAny(BodyResolveMode.FULL) }
var currentElement = locationElement.declarationContainer(false)
while (currentElement != null) {
var entryPointContainer = currentElement
if (entryPointContainer is KtClass) {
entryPointContainer = entryPointContainer.companionObjects.singleOrNull()
}
if (entryPointContainer != null && mainFunctionDetector.hasMain(entryPointContainer.declarations)) return entryPointContainer
currentElement = (currentElement as PsiElement).declarationContainer(true)
}
return null
}
fun getStartClassFqName(container: KtDeclarationContainer?): String? = when(container) {
null -> null
is KtFile -> container.javaFileFacadeFqName.asString()
is KtClassOrObject -> {
if (!container.isValid) {
null
}
else if (container is KtObjectDeclaration && container.isCompanion()) {
val containerClass = container.getParentOfType<KtClass>(true)
containerClass?.toLightClass()?.let { ClassUtil.getJVMClassName(it) }
}
else {
container.toLightClass()?.let { ClassUtil.getJVMClassName(it) }
}
}
else -> throw IllegalArgumentException("Invalid entry-point container: " + (container as PsiElement).text)
}
private fun PsiElement.declarationContainer(strict: Boolean): KtDeclarationContainer? {
val element = if (strict)
PsiTreeUtil.getParentOfType(this, KtClassOrObject::class.java, KtFile::class.java)
else
PsiTreeUtil.getNonStrictParentOfType(this, KtClassOrObject::class.java, KtFile::class.java)
return element as KtDeclarationContainer?
}
}
}
fun Module.findJvmImplementationModule(): Module? {
if (!platform.isCommon) return null
return implementingModules.firstOrNull { it.platform.isJvm }
}
@@ -0,0 +1,17 @@
/*
* 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.run
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.platform.impl.isJvm
fun Module.asJvmModule(): Module? {
if (platform.isJvm) return this
return null
}
@@ -0,0 +1,22 @@
/*
* 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.run
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.caches.project.implementingModules
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.platform.impl.isCommon
import org.jetbrains.kotlin.platform.impl.isJvm
fun Module.asJvmModule(): Module? {
if (platform.isJvm) return this
if (platform.isCommon) {
return implementingModules.firstOrNull { it.platform.isJvm }
}
return null
}