diff --git a/ultimate/.idea/libraries/javascript_support.xml b/ultimate/.idea/libraries/javascript_support.xml new file mode 100644 index 00000000000..42d04992324 --- /dev/null +++ b/ultimate/.idea/libraries/javascript_support.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ultimate/.idea/libraries/nodejs_support.xml b/ultimate/.idea/libraries/nodejs_support.xml new file mode 100644 index 00000000000..9ffe5f21fba --- /dev/null +++ b/ultimate/.idea/libraries/nodejs_support.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ultimate/kotlin-ultimate.iml b/ultimate/kotlin-ultimate.iml index a56e25ea93e..aa0f2ecea8a 100644 --- a/ultimate/kotlin-ultimate.iml +++ b/ultimate/kotlin-ultimate.iml @@ -27,6 +27,8 @@ + + diff --git a/ultimate/resources/META-INF/kotlin-nodejs.xml b/ultimate/resources/META-INF/kotlin-nodejs.xml new file mode 100644 index 00000000000..8771420d1db --- /dev/null +++ b/ultimate/resources/META-INF/kotlin-nodejs.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/ultimate/resources/META-INF/ultimate-plugin.xml b/ultimate/resources/META-INF/ultimate-plugin.xml index 2e4d2b1029f..dfd991b050e 100644 --- a/ultimate/resources/META-INF/ultimate-plugin.xml +++ b/ultimate/resources/META-INF/ultimate-plugin.xml @@ -6,4 +6,5 @@ com.intellij.css com.intellij.jsp com.intellij.diagram + NodeJS \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt b/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt new file mode 100644 index 00000000000..36235d8e754 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/js/jsModuleUtils.kt @@ -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) { + val compilerExtension = CompilerModuleExtension.getInstance(target) ?: return + result.addIfNotNull(compilerExtension.compilerOutputPath?.path) + result.addIfNotNull(compilerExtension.compilerOutputPathForTests?.let { "${it.path}/lib" }) +} + +fun getJsClasspath(module: Module): List { + val result = ArrayList() + ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule { + addSingleModulePaths(it, result) + true + } + return result +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/nodejs/KotlinMochaRunConfigurationProducer.kt b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/KotlinMochaRunConfigurationProducer.kt new file mode 100644 index 00000000000..47b996f8617 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/nodejs/KotlinMochaRunConfigurationProducer.kt @@ -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, val testName: String?) + + // Copied from MochaRunConfigurationProducer.collectMochaTestRoots() + private fun collectMochaTestRoots(project: Project): List { + return RunManager + .getInstance(project) + .getConfigurationsList(MochaConfigurationType.getInstance()) + .filterIsInstance() + .mapNotNullTo(SmartList()) { 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() + 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() ?: return null + val klass = when (declaration) { + is KtClassOrObject -> declaration + is KtNamedFunction -> declaration.containingClassOrObject ?: return null + else -> return null + } + val suiteNames = klass.parentsWithSelf + .filterIsInstance() + .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 + ): 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 + } +} \ No newline at end of file diff --git a/ultimate/update_dependencies.xml b/ultimate/update_dependencies.xml index a96f1ddbf6d..9638f4e5b59 100644 --- a/ultimate/update_dependencies.xml +++ b/ultimate/update_dependencies.xml @@ -25,6 +25,13 @@ + + + + + + + @@ -33,6 +40,8 @@ + +