Implement test icon provider for 'native' platform

This commit is contained in:
Yan Zhulanow
2019-09-02 18:26:32 +09:00
parent c19d62af73
commit 8f4063b36c
6 changed files with 123 additions and 61 deletions
@@ -11,28 +11,21 @@ import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.gradle.KotlinPlatform
import org.jetbrains.kotlin.idea.compiler.configuration.Kotlin2JsCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
import org.jetbrains.kotlin.idea.framework.JSLibraryStdDescription
import org.jetbrains.kotlin.idea.framework.JsLibraryStdDetectionUtil
import org.jetbrains.kotlin.idea.highlighter.KotlinTestRunLineMarkerContributor.Companion.getTestStateIcon
import org.jetbrains.kotlin.idea.js.KotlinJSRunConfigurationData
import org.jetbrains.kotlin.idea.js.KotlinJSRunConfigurationDataProvider
import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling
import org.jetbrains.kotlin.idea.platform.getGenericTestIcon
import org.jetbrains.kotlin.idea.run.multiplatform.KotlinMultiplatformRunLocationsProvider
import org.jetbrains.kotlin.idea.util.string.joinWithEscape
import org.jetbrains.kotlin.js.resolve.JsResolverForModuleFactory
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.impl.JsIdePlatformKind
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.ifEmpty
import javax.swing.Icon
@@ -40,9 +33,6 @@ import javax.swing.Icon
class JsIdePlatformKindTooling : IdePlatformKindTooling() {
companion object {
private const val MAVEN_OLD_JS_STDLIB_ID = "kotlin-js-library"
private val TEST_FQ_NAME = FqName("kotlin.test.Test")
private val IGNORE_FQ_NAME = FqName("kotlin.test.Ignore")
}
override val kind = JsIdePlatformKind
@@ -61,39 +51,26 @@ class JsIdePlatformKindTooling : IdePlatformKindTooling() {
}
override fun getTestIcon(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor): Icon? {
if (!descriptor.isTest()) return null
return getGenericTestIcon(declaration, descriptor) {
val contexts by lazy { computeConfigurationContexts(declaration) }
val contexts by lazy { computeConfigurationContexts(declaration) }
val runConfigData = RunConfigurationProducer
.getProducers(declaration.project)
.asSequence()
.filterIsInstance<KotlinJSRunConfigurationDataProvider<*>>()
.filter { it.isForTests }
.flatMap { provider -> contexts.map { context -> provider.getConfigurationData(context) } }
.firstOrNull { it != null }
?: return@getGenericTestIcon null
val runConfigData = RunConfigurationProducer
.getProducers(declaration.project)
.asSequence()
.filterIsInstance<KotlinJSRunConfigurationDataProvider<*>>()
.filter { it.isForTests }
.flatMap { provider -> contexts.map { context -> provider.getConfigurationData(context) } }
.firstOrNull { it != null } ?: return null
val location = if (runConfigData is KotlinJSRunConfigurationData) {
FileUtil.toSystemDependentName(runConfigData.jsOutputFilePath)
} else {
declaration.containingKtFile.packageFqName.asString()
}
val locations = ArrayList<String>()
locations += FileUtil.toSystemDependentName(runConfigData.jsOutputFilePath)
val klass = when (declaration) {
is KtClassOrObject -> declaration
is KtNamedFunction -> declaration.containingClassOrObject ?: return null
else -> return null
return@getGenericTestIcon SmartList(location)
}
locations += klass.parentsWithSelf.filterIsInstance<KtNamedDeclaration>().mapNotNull { it.name }.toList().asReversed()
val testName = (declaration as? KtNamedFunction)?.name
if (testName != null) {
locations += "$testName"
}
val prefix = if (testName != null) "test://" else "suite://"
val url = prefix + locations.joinWithEscape('.')
return getTestStateIcon(url, declaration.project)
}
override fun acceptsAsEntryPoint(function: KtFunction): Boolean {
@@ -114,17 +91,4 @@ class JsIdePlatformKindTooling : IdePlatformKindTooling() {
ConfigurationContext.createEmptyContextForLocation(it)
}.ifEmpty { listOf(ConfigurationContext.createEmptyContextForLocation(location)) }.asSequence()
}
private fun DeclarationDescriptor.isIgnored(): Boolean =
annotations.any { it.fqName == IGNORE_FQ_NAME } || ((containingDeclaration as? ClassDescriptor)?.isIgnored() ?: false)
private fun DeclarationDescriptor.isTest(): Boolean {
if (isIgnored()) return false
if (annotations.any { it.fqName == TEST_FQ_NAME }) return true
if (this is ClassDescriptorWithResolutionScopes) {
return declaredCallableMembers.any { it.isTest() }
}
return false
}
}
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.jvm.JvmResolverForModuleFactory
import org.jetbrains.kotlin.utils.PathUtil
import javax.swing.Icon
@@ -73,7 +72,7 @@ class JvmIdePlatformKindTooling : IdePlatformKindTooling() {
else -> return null
}
return getTestStateIcon(url, declaration.project) ?: framework.icon
return getTestStateIcon(url, declaration.project, strict = false) ?: framework.icon
}
override fun acceptsAsEntryPoint(function: KtFunction) = true
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.platform
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.highlighter.KotlinTestRunLineMarkerContributor.Companion.getTestStateIcon
import org.jetbrains.kotlin.idea.util.string.joinWithEscape
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import javax.swing.Icon
private val TEST_FQ_NAME = FqName("kotlin.test.Test")
private val IGNORE_FQ_NAME = FqName("kotlin.test.Ignore")
fun getGenericTestIcon(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor, initialLocations: () -> List<String>?): Icon? {
if (!descriptor.isKotlinTestDeclaration()) return null
val locations = initialLocations()?.toMutableList() ?: return null
val clazz = when (declaration) {
is KtClassOrObject -> declaration
is KtNamedFunction -> declaration.containingClassOrObject ?: return null
else -> return null
}
locations += clazz.parentsWithSelf.filterIsInstance<KtNamedDeclaration>()
.mapNotNull { it.name }
.toList().asReversed()
val testName = (declaration as? KtNamedFunction)?.name
if (testName != null) {
locations += "$testName"
}
val prefix = if (testName != null) "test://" else "suite://"
val url = prefix + locations.joinWithEscape('.')
val javaUrl = "java:$url"
val project = declaration.project
return getTestStateIcon(javaUrl, project, strict = true) ?: getTestStateIcon(url, project, strict = false)
}
private tailrec fun DeclarationDescriptor.isIgnored(): Boolean {
if (annotations.any { it.fqName == IGNORE_FQ_NAME }) {
return true
}
val containingClass = containingDeclaration as? ClassDescriptor ?: return false
return containingClass.isIgnored()
}
fun DeclarationDescriptor.isKotlinTestDeclaration(): Boolean {
if (isIgnored()) {
return false
}
if (annotations.any { it.fqName == TEST_FQ_NAME }) {
return true
}
val classDescriptor = this as? ClassDescriptorWithResolutionScopes ?: return false
return classDescriptor.declaredCallableMembers.any { it.isKotlinTestDeclaration() }
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.ide.konan
interface KotlinNativeRunConfigurationProvider {
val isForTests: Boolean
}
@@ -5,17 +5,18 @@
package org.jetbrains.kotlin.ide.konan
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.DummyLibraryProperties
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.PersistentLibraryKind
import com.intellij.openapi.roots.ui.configuration.libraries.CustomLibraryDescription
import org.jetbrains.kotlin.ide.konan.analyzer.NativeResolverForModuleFactory
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.gradle.KotlinPlatform
import org.jetbrains.kotlin.idea.framework.KotlinLibraryKind
import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling
import org.jetbrains.kotlin.idea.platform.getGenericTestIcon
import org.jetbrains.kotlin.platform.impl.NativeIdePlatformKind
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -37,7 +38,21 @@ class NativeIdePlatformKindTooling : IdePlatformKindTooling() {
override fun getLibraryDescription(project: Project): CustomLibraryDescription? = null
override fun getLibraryVersionProvider(project: Project): (Library) -> String? = { null }
override fun getTestIcon(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor): Icon? = null
override fun getTestIcon(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor): Icon? {
return getGenericTestIcon(declaration, descriptor) {
val availableRunConfigurations = RunConfigurationProducer
.getProducers(declaration.project)
.asSequence()
.filterIsInstance<KotlinNativeRunConfigurationProvider>()
.filter { it.isForTests }
if (availableRunConfigurations.firstOrNull() == null) {
return@getGenericTestIcon null
}
return@getGenericTestIcon emptyList()
}
}
override fun acceptsAsEntryPoint(function: KtFunction) = false
}
@@ -38,9 +38,11 @@ import javax.swing.Icon
class KotlinTestRunLineMarkerContributor : RunLineMarkerContributor() {
companion object {
fun getTestStateIcon(url: String, project: Project): Icon? {
fun getTestStateIcon(url: String, project: Project, strict: Boolean): Icon? {
val defaultIcon = AllIcons.RunConfigurations.TestState.Run
val state = TestStateStorage.getInstance(project).getState(url) ?: return defaultIcon
val state = TestStateStorage.getInstance(project).getState(url)
?: return if (strict) null else defaultIcon
return when (TestIconMapper.getMagnitude(state.magnitude)) {
TestStateInfo.Magnitude.ERROR_INDEX,
TestStateInfo.Magnitude.FAILED_INDEX -> AllIcons.RunConfigurations.TestState.Red2