Kotlin Ultimate: Support Mocha run configurations for Kotlin sources

#KT-16814 In Progress
This commit is contained in:
Alexey Sedunov
2017-08-16 12:57:27 +03:00
parent 4f647e84c9
commit dcd966f7c2
8 changed files with 233 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="javascript-support">
<CLASSES>
<root url="file://$PROJECT_DIR$/ideaSDK/plugins/JavaScriptLanguage/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$PROJECT_DIR$/ideaSDK/plugins/JavaScriptLanguage/lib" recursive="false" />
</library>
</component>

View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="nodejs-support">
<CLASSES>
<root url="file://$PROJECT_DIR$/dependencies/nodejs_plugin/NodeJS/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$PROJECT_DIR$/dependencies/nodejs_plugin/NodeJS/lib" recursive="false" />
</library>
</component>

View File

@@ -27,6 +27,8 @@
<orderEntry type="library" scope="PROVIDED" name="gradle-and-groovy-plugin" level="project" />
<orderEntry type="library" scope="PROVIDED" name="junit-plugin" level="project" />
<orderEntry type="library" scope="PROVIDED" name="diagram-support" level="project" />
<orderEntry type="library" scope="PROVIDED" name="javascript-support" level="project" />
<orderEntry type="library" scope="PROVIDED" name="nodejs-support" level="project" />
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="library" name="protobuf" level="project" />

View File

@@ -0,0 +1,5 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.KotlinMochaRunConfigurationProducer"/>
</extensions>
</idea-plugin>

View File

@@ -6,4 +6,5 @@
<depends optional="true">com.intellij.css</depends>
<depends optional="true">com.intellij.jsp</depends>
<depends optional="true">com.intellij.diagram</depends>
<depends optional="true" config-file="kotlin-nodejs.xml">NodeJS</depends>
</idea-plugin>

View File

@@ -0,0 +1,38 @@
/*
* 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
}

View File

@@ -0,0 +1,158 @@
package org.jetbrains.kotlin.idea.nodejs
import com.intellij.execution.RunManager
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.lang.javascript.ecmascript6.TypeScriptUtil
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.SmartList
import com.intellij.util.containers.SmartHashSet
import com.jetbrains.nodejs.mocha.MochaUtil
import com.jetbrains.nodejs.mocha.execution.*
import com.jetbrains.nodejs.util.NodeJsCoffeeUtil
import org.jetbrains.kotlin.idea.js.getJsClasspath
import org.jetbrains.kotlin.idea.js.getJsOutputFilePath
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.js.resolve.JsPlatform
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.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import java.io.File
class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
private data class TestElementInfo(val runSettings: MochaRunSettings, val enclosingTestElement: PsiElement)
private data class TestElementPath(val suiteNames: List<String>, val testName: String?)
// Copied from MochaRunConfigurationProducer.collectMochaTestRoots()
private fun collectMochaTestRoots(project: Project): List<VirtualFile> {
return RunManager
.getInstance(project)
.getConfigurationsList(MochaConfigurationType.getInstance())
.filterIsInstance<MochaRunConfiguration>()
.mapNotNullTo(SmartList<VirtualFile>()) { configuration ->
val settings = configuration.runSettings
val path = when (settings.testKind) {
MochaTestKind.DIRECTORY -> settings.testDirPath
MochaTestKind.TEST_FILE,
MochaTestKind.SUITE,
MochaTestKind.TEST -> settings.testFilePath
else -> null
}
if (path.isNullOrBlank()) return@mapNotNullTo null
LocalFileSystem.getInstance().findFileByPath(path!!)
}
}
// 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
if (context.getOriginalConfiguration(MochaConfigurationType.getInstance()) is MochaRunConfiguration) return true
val roots = collectMochaTestRoots(element.project)
if (roots.isEmpty()) return false
val dirs = SmartHashSet<VirtualFile>()
for (root in roots) {
if (root.isDirectory) {
dirs.add(root)
}
else if (root == file) return true
}
return VfsUtilCore.isUnder(file, dirs)
}
private fun createSuiteOrTestData(element: PsiElement): TestElementPath? {
val declaration = element.getNonStrictParentOfType<KtNamedDeclaration>() ?: return null
val klass = when (declaration) {
is KtClassOrObject -> declaration
is KtNamedFunction -> declaration.containingClassOrObject ?: return null
else -> return null
}
val suiteNames = klass.parentsWithSelf
.filterIsInstance<KtClassOrObject>()
.mapNotNull { it.name }
.toList()
.asReversed()
val testName = (declaration as? KtNamedFunction)?.name
return TestElementPath(suiteNames, testName)
}
private fun createTestElementRunInfo(element: PsiElement, originalSettings: MochaRunSettings): TestElementInfo? {
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return null
if (TargetPlatformDetector.getPlatform(module) !is JsPlatform) return null
val project = module.project
val testFilePath = getJsOutputFilePath(module, true, false) ?: return null
val settings = if (originalSettings.workingDir.isBlank()) {
val workingDir = FileUtil.toSystemDependentName(project.baseDir.path)
originalSettings.builder().setWorkingDir(workingDir).build()
}
else originalSettings
val (suiteNames, testName) = createSuiteOrTestData(element) ?: return null
val builder = settings.builder()
builder.setTestFilePath(testFilePath)
if (settings.ui.isEmpty()) {
builder.setUi(MochaUtil.UI_BDD)
}
if (testName == null) {
builder.setTestKind(MochaTestKind.SUITE)
builder.setSuiteNames(suiteNames)
}
else {
builder.setTestKind(MochaTestKind.TEST)
builder.setTestNames(suiteNames + testName)
}
val nodeJsClasspath = getJsClasspath(module).joinToString(File.pathSeparator) {
val basePath = project.basePath ?: return@joinToString it
FileUtil.getRelativePath(basePath, it, '/') ?: it
}
builder.setEnvData(EnvironmentVariablesData.create(mapOf("NODE_PATH" to nodeJsClasspath), true))
return TestElementInfo(builder.build(), element)
}
override fun isConfigurationFromCompatibleContext(configuration: MochaRunConfiguration, context: ConfigurationContext): Boolean {
val element = context.psiLocation ?: return false
val (thisRunSettings, _) = createTestElementRunInfo(element, configuration.runSettings) ?: return false
val thatRunSettings = configuration.runSettings
val thisTestKind = thisRunSettings.testKind
if (thisTestKind != thatRunSettings.testKind) return false
return when {
thisTestKind == MochaTestKind.DIRECTORY -> thisRunSettings.testDirPath == thatRunSettings.testDirPath
thisTestKind == MochaTestKind.PATTERN -> thisRunSettings.testFilePattern == thatRunSettings.testFilePattern
thisTestKind == MochaTestKind.TEST_FILE -> thisRunSettings.testFilePath == thatRunSettings.testFilePath
thisTestKind == MochaTestKind.SUITE -> thisRunSettings.testFilePath == thatRunSettings.testFilePath && thisRunSettings.suiteNames == thatRunSettings.suiteNames
thisTestKind != MochaTestKind.TEST -> false
else -> thisRunSettings.testFilePath == thatRunSettings.testFilePath && thisRunSettings.testNames == thatRunSettings.testNames
}
}
override fun setupConfigurationFromCompatibleContext(
configuration: MochaRunConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>
): Boolean {
val element = context.psiLocation ?: return false
if (!isActiveFor(element, context)) return false
val (runSettings, enclosingTestElement) = createTestElementRunInfo(element, configuration.runSettings) ?: return false
if (runSettings.testKind == MochaTestKind.DIRECTORY) return false
configuration.runSettings = runSettings
sourceElement.set(enclosingTestElement)
configuration.setGeneratedName()
return true
}
}

View File

@@ -25,6 +25,13 @@
</sequential>
</macrodef>
<macrodef name="get-nodejs-intellij-plugin">
<sequential>
<get src="http://plugins.jetbrains.com/plugin/download?updateId=37668" dest="${download}/nodejs_plugin.zip" usetimestamp="true"/>
<unzip src="${download}/nodejs_plugin.zip" dest="dependencies/nodejs_plugin" overwrite="true"/>
</sequential>
</macrodef>
<target name="fetch-extras">
<mkdir dir="${download}"/>
@@ -33,6 +40,8 @@
<get-spring-library lib="spring-context" version="4.2.0.RELEASE"/>
<get-spring-library lib="spring-tx" version="4.2.0.RELEASE"/>
<get-spring-library lib="spring-web" version="4.2.0.RELEASE"/>
<get-nodejs-intellij-plugin/>
</target>
<!-- Override fetch-third-party from the main buildfile -->