MPP: Support Mocha/Protractor run configuration for common modules

This is useful when they are implemented by JavaScript modules
This commit is contained in:
Alexey Sedunov
2017-10-26 15:08:00 +03:00
parent 3319fdba6d
commit 97332aaeea
6 changed files with 95 additions and 67 deletions
@@ -23,7 +23,6 @@ import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.execution.testframework.TestIconMapper
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
import com.intellij.icons.AllIcons
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
@@ -34,8 +33,10 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.js.getJsOutputFilePath
import org.jetbrains.kotlin.idea.js.jsOrJsImpl
import org.jetbrains.kotlin.idea.js.jsTestOutputFilePath
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.idea.util.string.joinWithEscape
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.name.FqName
@@ -45,6 +46,7 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.TargetPlatform.Common
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import javax.swing.Icon
@@ -108,8 +110,8 @@ class KotlinTestRunLineMarkerContributor : RunLineMarkerContributor() {
private fun getJavaScriptTestIcon(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor): Icon? {
if (!descriptor.isTest()) return null
val module = ModuleUtilCore.findModuleForPsiElement(declaration) ?: return null
val testFilePath = getJsOutputFilePath(module, true, false) ?: return null
val module = declaration.module?.jsOrJsImpl() ?: return null
val testFilePath = module.jsTestOutputFilePath ?: return null
val locations = ArrayList<String>()
@@ -146,7 +148,7 @@ class KotlinTestRunLineMarkerContributor : RunLineMarkerContributor() {
val platform = TargetPlatformDetector.getPlatform(declaration.containingKtFile)
val icon = when (platform) {
is JvmPlatform -> getJavaTestIcon(declaration)
is JsPlatform -> getJavaScriptTestIcon(declaration, descriptor)
is JsPlatform, is Common -> getJavaScriptTestIcon(declaration, descriptor)
else -> return null
} ?: return null
return RunLineMarkerContributor.Info(icon, { "Run Test" }, ExecutorAction.getActions())
@@ -19,14 +19,23 @@ package org.jetbrains.kotlin.idea.js
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.CompilerModuleExtension
import org.jetbrains.jps.util.JpsPathUtil
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.facet.implementingModules
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.resolve.TargetPlatform
fun getJsOutputFilePath(module: Module, isTests: Boolean, isMeta: Boolean): String? {
val compilerExtension = CompilerModuleExtension.getInstance(module)
val outputDir = (if (isTests) compilerExtension?.compilerOutputUrlForTests else compilerExtension?.compilerOutputUrl)
?: return null
val extension = if (isMeta) KotlinJavascriptMetadataUtils.META_JS_SUFFIX else KotlinJavascriptMetadataUtils.JS_EXT
return JpsPathUtil.urlToPath("$outputDir/${module.name}${suffix(isTests)}$extension")
}
val Module.jsTestOutputFilePath: String?
get() {
(KotlinFacet.get(this)?.configuration?.settings?.testOutputPath)?.let { return it }
private fun suffix(isTests: Boolean) = if (isTests) "_test" else ""
val compilerExtension = CompilerModuleExtension.getInstance(this)
val outputDir = compilerExtension?.compilerOutputUrlForTests ?: return null
return JpsPathUtil.urlToPath("$outputDir/${name}_test.js")
}
fun Module.jsOrJsImpl() = when (TargetPlatformDetector.getPlatform(this)) {
is TargetPlatform.Common -> implementingModules.firstOrNull { TargetPlatformDetector.getPlatform(it) is JsPlatform }
is JsPlatform -> this
else -> null
}
@@ -1,38 +0,0 @@
/*
* Copyright 2010-2017 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.js
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.CompilerModuleExtension
import com.intellij.openapi.roots.ModuleRootManager
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
private fun addSingleModulePaths(target: Module, result: MutableList<String>) {
val compilerExtension = CompilerModuleExtension.getInstance(target) ?: return
result.addIfNotNull(compilerExtension.compilerOutputPath?.path)
result.addIfNotNull(compilerExtension.compilerOutputPathForTests?.let { "${it.path}/lib" })
}
fun getJsClasspath(module: Module): List<String> {
val result = ArrayList<String>()
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule {
addSingleModulePaths(it, result)
true
}
return result
}
@@ -33,10 +33,12 @@ import com.intellij.util.SmartList
import com.intellij.util.containers.SmartHashSet
import com.jetbrains.nodejs.mocha.MochaUtil
import com.jetbrains.nodejs.mocha.execution.*
import org.jetbrains.kotlin.idea.js.getJsOutputFilePath
import org.jetbrains.kotlin.idea.js.jsOrJsImpl
import org.jetbrains.kotlin.idea.js.jsTestOutputFilePath
import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -74,12 +76,21 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
// Copied from MochaRunConfigurationProducer.isActiveFor()
private fun isActiveFor(element: PsiElement, context: ConfigurationContext): Boolean {
val file = PsiUtilCore.getVirtualFile(element) ?: return false
if (isTestRunnerPackageAvailableFor(element.project, file)) return true
val module = element.module
val jsModule = module?.jsOrJsImpl() ?: return false
val file = if (jsModule != module) {
jsModule.moduleFile
}
else {
PsiUtilCore.getVirtualFile(element)
} ?: return false
val project = module.project
if (isTestRunnerPackageAvailableFor(project, file)) return true
if (context.getOriginalConfiguration(MochaConfigurationType.getInstance()) is MochaRunConfiguration) return true
val roots = collectMochaTestRoots(element.project)
val roots = collectMochaTestRoots(project)
if (roots.isEmpty()) return false
val dirs = SmartHashSet<VirtualFile>()
@@ -113,10 +124,9 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
}
private fun createTestElementRunInfo(element: PsiElement, originalSettings: MochaRunSettings): TestElementInfo? {
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return null
if (TargetPlatformDetector.getPlatform(module) !is JsPlatform) return null
val module = element.module?.jsOrJsImpl() ?: return null
val project = module.project
val testFilePath = getJsOutputFilePath(module, true, false) ?: return null
val testFilePath = module.jsTestOutputFilePath ?: return null
val settings = if (originalSettings.workingDir.isBlank()) {
val workingDir = FileUtil.toSystemDependentName(project.baseDir.path)
originalSettings.builder().setWorkingDir(workingDir).build()
@@ -17,17 +17,59 @@
package org.jetbrains.kotlin.idea.nodejs
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.CompilerModuleExtension
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.js.getJsClasspath
import com.intellij.util.PathUtil
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.plugins.gradle.model.ExternalProject
import org.jetbrains.plugins.gradle.service.project.data.ExternalProjectDataCache
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.io.File
import java.util.*
const val NODE_PATH_VAR = "NODE_PATH"
fun Module.getNodeJsEnvironmentVars(): EnvironmentVariablesData {
val nodeJsClasspath = getJsClasspath(this).joinToString(File.pathSeparator) {
val nodeJsClasspath = getNodeJsClasspath(this).joinToString(File.pathSeparator) {
val basePath = project.basePath ?: return@joinToString it
FileUtil.getRelativePath(basePath, it, '/') ?: it
}
return EnvironmentVariablesData.create(mapOf(NODE_PATH_VAR to nodeJsClasspath), true)
}
private fun addSingleModulePaths(target: Module, result: MutableList<String>) {
val compilerExtension = CompilerModuleExtension.getInstance(target) ?: return
result.addIfNotNull(compilerExtension.compilerOutputPath?.path)
result.addIfNotNull(compilerExtension.compilerOutputPathForTests?.let { "${it.path}/lib" })
}
private fun ExternalProject.findProjectById(id: String): ExternalProject? {
if (this.id == id) return this
for (childProject in childProjects.values) {
childProject.findProjectById(id)?.let { return it }
}
return null
}
private fun getNodeJsClasspath(module: Module): List<String> {
if (KotlinPluginUtil.isGradleModule(module)) {
val gradleProjectPath = ExternalSystemModulePropertyManager.getInstance(module).getRootProjectPath()
val projectCache = ExternalProjectDataCache.getInstance(module.project)
val rootProject = projectCache.getRootExternalProject(GradleConstants.SYSTEM_ID, File(gradleProjectPath)) ?: return listOf()
val externalProjectId = ExternalSystemApiUtil.getExternalProjectId(module) ?: return listOf()
val buildDir = rootProject.findProjectById(externalProjectId)?.buildDir ?: return listOf()
return listOfNotNull(PathUtil.toSystemIndependentName(buildDir.absolutePath) + "/nodejs_modules")
}
val result = ArrayList<String>()
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule {
addSingleModulePaths(it, result)
true
}
return result
}
@@ -23,9 +23,12 @@ import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.js.getJsOutputFilePath
import org.jetbrains.kotlin.idea.facet.implementingModules
import org.jetbrains.kotlin.idea.js.jsOrJsImpl
import org.jetbrains.kotlin.idea.js.jsTestOutputFilePath
import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.resolve.TargetPlatform.Common
import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.js.resolve.JsPlatform
@@ -37,8 +40,8 @@ class KotlinProtractorRunConfigurationProducer :
context: ConfigurationContext
): Boolean {
val contextPsi = context.psiLocation ?: return false
val module = contextPsi.module ?: return false
val testFilePath = getJsOutputFilePath(module, isTests = true, isMeta = false) ?: return false
val jsModule = contextPsi.module?.jsOrJsImpl() ?: return false
val testFilePath = jsModule.jsTestOutputFilePath ?: return false
return configuration.runSettings.testFileSystemDependentPath == FileUtil.toSystemDependentName(testFilePath)
}
@@ -50,13 +53,13 @@ class KotlinProtractorRunConfigurationProducer :
val element = context.psiLocation ?: return false
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return false
if (element !is PsiDirectory || module.getModuleDir() != element.virtualFile.path) return false
if (TargetPlatformDetector.getPlatform(module) !is JsPlatform) return false
val testFilePath = getJsOutputFilePath(module, true, false) ?: return false
val jsModule = module.jsOrJsImpl() ?: return false
val testFilePath = jsModule.jsTestOutputFilePath ?: return false
sourceElement.set(element)
configuration.runSettings = configuration.runSettings.copy(
testFilePath = testFilePath,
envData = module.getNodeJsEnvironmentVars()
envData = jsModule.getNodeJsEnvironmentVars()
)
configuration.name = configuration.suggestedName()
return true