Skip to content

Commit 0270f4c

Browse files
authored
Added Node JS getter from IDEA setting with automatically NPM packages installation (#1566)
1 parent 2959469 commit 0270f4c

File tree

5 files changed

+116
-60
lines changed

5 files changed

+116
-60
lines changed

utbot-intellij-js/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies {
3636
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
3737
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
3838
implementation(project(":utbot-ui-commons"))
39+
implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion)
3940

4041
//Family
4142
implementation(project(":utbot-js"))

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogProcessor.kt

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.utbot.intellij.plugin.language.js
22

33
import api.JsTestGenerator
44
import com.intellij.codeInsight.CodeInsightUtil
5+
import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreterManager
56
import com.intellij.lang.ecmascript6.psi.ES6Class
67
import com.intellij.lang.javascript.psi.JSFile
78
import com.intellij.lang.javascript.refactoring.util.JSMemberInfo
@@ -12,10 +13,13 @@ import com.intellij.openapi.module.Module
1213
import com.intellij.openapi.progress.ProgressIndicator
1314
import com.intellij.openapi.progress.Task
1415
import com.intellij.openapi.project.Project
16+
import com.intellij.openapi.ui.Messages
1517
import com.intellij.psi.PsiDocumentManager
1618
import com.intellij.psi.PsiFileFactory
1719
import com.intellij.psi.impl.file.PsiDirectoryFactory
1820
import com.intellij.util.concurrency.AppExecutorUtil
21+
import framework.codegen.Mocha
22+
import mu.KotlinLogging
1923
import org.jetbrains.kotlin.idea.util.application.invokeLater
2024
import org.jetbrains.kotlin.idea.util.application.runReadAction
2125
import org.jetbrains.kotlin.idea.util.application.runWriteAction
@@ -26,6 +30,9 @@ import settings.JsDynamicSettings
2630
import settings.JsExportsSettings.endComment
2731
import settings.JsExportsSettings.startComment
2832
import settings.JsTestGenerationSettings.dummyClassName
33+
import utils.JsCmdExec
34+
35+
private val logger = KotlinLogging.logger {}
2936

3037
object JsDialogProcessor {
3138

@@ -38,28 +45,37 @@ object JsDialogProcessor {
3845
editor: Editor,
3946
file: JSFile
4047
) {
41-
createDialog(project, srcModule, fileMethods, focusedMethod, containingFilePath, file)?.let { dialogProcessor ->
42-
if (!dialogProcessor.showAndGet()) return
43-
/*
44-
Since Tern.js accesses containing file, sync with file system required before test generation.
45-
*/
46-
runWriteAction {
47-
with(FileDocumentManager.getInstance()) {
48-
saveDocument(editor.document)
48+
val model = createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file)
49+
(object : Task.Backgroundable(
50+
project,
51+
"Check the requirements"
52+
) {
53+
override fun run(indicator: ProgressIndicator) {
54+
invokeLater {
55+
getFrameworkLibraryPath(Mocha.displayName.lowercase(), model)
56+
createDialog(model)?.let { dialogProcessor ->
57+
if (!dialogProcessor.showAndGet()) return@invokeLater
58+
// Since Tern.js accesses containing file, sync with file system required before test generation.
59+
runWriteAction {
60+
with(FileDocumentManager.getInstance()) {
61+
saveDocument(editor.document)
62+
}
63+
}
64+
createTests(dialogProcessor.model, containingFilePath, editor)
65+
}
4966
}
5067
}
51-
createTests(dialogProcessor.model, containingFilePath, editor)
52-
}
68+
}).queue()
5369
}
5470

55-
private fun createDialog(
71+
private fun createJsTestModel(
5672
project: Project,
5773
srcModule: Module,
5874
fileMethods: Set<JSMemberInfo>,
5975
focusedMethod: JSMemberInfo?,
6076
filePath: String,
6177
file: JSFile
62-
): JsDialogWindow? {
78+
): JsTestsModel? {
6379
val testModules = srcModule.testModules(project)
6480

6581
if (testModules.isEmpty()) {
@@ -70,19 +86,42 @@ object JsDialogProcessor {
7086
showErrorDialogLater(project, errorMessage, "Test source roots not found")
7187
return null
7288
}
89+
return JsTestsModel(
90+
project = project,
91+
srcModule = srcModule,
92+
potentialTestModules = testModules,
93+
fileMethods = fileMethods,
94+
selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(),
95+
file = file
96+
).apply {
97+
containingFilePath = filePath
98+
}
7399

74-
return JsDialogWindow(
75-
JsTestsModel(
76-
project = project,
77-
srcModule = srcModule,
78-
potentialTestModules = testModules,
79-
fileMethods = fileMethods,
80-
selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(),
81-
file = file
82-
).apply {
83-
containingFilePath = filePath
84-
}
85-
)
100+
}
101+
102+
private fun createDialog(
103+
jsTestsModel: JsTestsModel?
104+
): JsDialogWindow? {
105+
try {
106+
jsTestsModel?.pathToNode = NodeJsLocalInterpreterManager.getInstance()
107+
.interpreters.first().interpreterSystemIndependentPath
108+
val (_, error) = JsCmdExec.runCommand(
109+
shouldWait = true,
110+
cmd = arrayOf("node", "-v")
111+
)
112+
if (error.readText().isNotEmpty()) throw NoSuchElementException()
113+
} catch (e: NoSuchElementException) {
114+
Messages.showErrorDialog(
115+
"Node.js interpreter is not found in IDEA settings.\n" +
116+
"Please set it in Settings > Languages & Frameworks > Node.js",
117+
"Requirement Error"
118+
)
119+
logger.error { "Node.js interpreter was not found in IDEA settings." }
120+
return null
121+
}
122+
return jsTestsModel?.let {
123+
JsDialogWindow(it)
124+
}
86125
}
87126

88127
private fun unblockDocument(project: Project, document: Document) {
@@ -207,3 +246,34 @@ object JsDialogProcessor {
207246
}
208247
}
209248
}
249+
250+
// TODO(MINOR): Add indicator.text for each installation
251+
fun installMissingRequirement(project: Project, pathToNPM: String, requirement: String) {
252+
val message = """
253+
Requirement is not installed:
254+
$requirement
255+
Install it?
256+
""".trimIndent()
257+
val result = Messages.showOkCancelDialog(
258+
project,
259+
message,
260+
"Requirement Missmatch Error",
261+
"Install",
262+
"Cancel",
263+
null
264+
)
265+
266+
if (result == Messages.CANCEL)
267+
return
268+
269+
val (_, errorStream) = installRequirement(pathToNPM, requirement, project.basePath)
270+
271+
val errorText = errorStream.readText()
272+
if (errorText.isNotEmpty()) {
273+
showErrorDialogLater(
274+
project,
275+
"Requirements installing failed with some reason:\n${errorText}",
276+
"Requirements error"
277+
)
278+
}
279+
}

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/JsDialogWindow.kt

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.utbot.intellij.plugin.language.js
22

3-
import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreterManager
43
import com.intellij.lang.javascript.refactoring.ui.JSMemberSelectionTable
54
import com.intellij.lang.javascript.refactoring.util.JSMemberInfo
65
import com.intellij.openapi.ui.ComboBox
@@ -37,18 +36,11 @@ class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) {
3736
this.preferredScrollableViewportSize = JBUI.size(-1, height)
3837
}
3938

40-
private val nodeInterp = try {
41-
NodeJsLocalInterpreterManager.getInstance().interpreters.first()
42-
} catch (e: NoSuchElementException) {
43-
throw IllegalStateException("Node.js interpreter is not set in the IDEA settings!")
44-
}
45-
4639
private val testSourceFolderField = TestSourceDirectoryChooser(model, model.file.virtualFile)
4740
private val testFrameworks = ComboBox(DefaultComboBoxModel(arrayOf(Mocha)))
4841
private val nycSourceFileChooserField = NycSourceFileChooser(model)
4942
private val coverageMode = CoverageModeButtons
5043

51-
// private var initTestFrameworkPresenceThread: Thread
5244
private lateinit var panel: DialogPanel
5345

5446
private val timeoutSpinner =
@@ -60,16 +52,8 @@ class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) {
6052
)
6153

6254
init {
63-
model.pathToNode = nodeInterp.interpreterSystemDependentPath.replace("\\", "/")
6455
model.pathToNPM = model.pathToNode.substringBeforeLast("/") + "/" + "npm"
65-
//TODO: Find out how to find pathToNode from IDEA settings without extra actions from the user
66-
model.pathToNode = "node"
6756
title = "Generate Tests with UtBot"
68-
// initTestFrameworkPresenceThread = thread(start = true) {
69-
// JsCgLanguageAssistant.getLanguageTestFrameworkManager().testFrameworks.forEach {
70-
// it.isInstalled = findFrameworkLibrary(it.displayName.lowercase(Locale.getDefault()), model)
71-
// }
72-
// }
7357
isResizable = false
7458
init()
7559
}

utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/language/js/Utils.kt

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,20 @@ package org.utbot.intellij.plugin.language.js
33
import com.intellij.openapi.ui.Messages
44
import utils.JsCmdExec
55
import utils.OsProvider
6+
import java.io.BufferedReader
67

7-
fun getFrameworkLibraryPath(npmPackageName: String, model: JsTestsModel): String? {
8+
fun getFrameworkLibraryPath(npmPackageName: String, model: JsTestsModel?): String? {
89
val (bufferedReader, errorReader) = JsCmdExec.runCommand(
9-
dir = model.project.basePath!!,
10+
dir = model?.project?.basePath!!,
1011
shouldWait = true,
1112
timeout = 10,
12-
cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), model.pathToNYC)
13+
cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), npmPackageName)
1314
)
1415
val input = bufferedReader.readText()
1516
val error = errorReader.readText()
1617

17-
if (error.isNotEmpty() or !input.contains(npmPackageName)) {
18-
if (findFrameworkLibrary(npmPackageName, model)) {
19-
Messages.showErrorDialog(
20-
model.project,
21-
"The following packages were not found, please set it in menu manually:\n $npmPackageName",
22-
"$npmPackageName missing!",
23-
)
24-
} else {
25-
Messages.showErrorDialog(
26-
model.project,
27-
"The following packages are not installed: $npmPackageName \nPlease install it via npm `> npm i -g $npmPackageName`",
28-
"$npmPackageName missing!",
29-
)
30-
}
18+
if ((error.isNotEmpty() or !input.contains(npmPackageName)) && !findFrameworkLibrary(npmPackageName, model)) {
19+
installMissingRequirement(model.project, model.pathToNPM, npmPackageName)
3120
return null
3221
}
3322
return input.substringBefore(npmPackageName) + npmPackageName
@@ -52,3 +41,15 @@ fun findFrameworkLibrary(npmPackageName: String, model: JsTestsModel): Boolean {
5241
}
5342
return checkForPackageText.contains(npmPackageName)
5443
}
44+
45+
fun installRequirement(pathToNPM: String, requirement: String, installingDir: String?): Pair<BufferedReader, BufferedReader> {
46+
val installationType = if (requirement == "mocha") "-l" else "-g"
47+
48+
val (buf1, buf2) = JsCmdExec.runCommand(
49+
dir = installingDir,
50+
shouldWait = true,
51+
timeout = 10,
52+
cmd = arrayOf(pathToNPM, "install", installationType) + requirement
53+
)
54+
return buf1 to buf2
55+
}

utbot-js/src/main/kotlin/utils/JsCmdExec.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package utils
22

3+
import org.utbot.framework.plugin.api.TimeoutException
4+
import settings.JsTestGenerationSettings.defaultTimeout
35
import java.io.BufferedReader
46
import java.io.File
57
import java.util.concurrent.TimeUnit
6-
import org.utbot.framework.plugin.api.TimeoutException
7-
import settings.JsTestGenerationSettings.defaultTimeout
88

99
object JsCmdExec {
1010

@@ -28,6 +28,6 @@ object JsCmdExec {
2828
throw TimeoutException("")
2929
}
3030
}
31-
return process.inputStream.bufferedReader() to process.errorStream.bufferedReader()
31+
return Pair(process.inputStream.bufferedReader(), process.errorStream.bufferedReader())
3232
}
3333
}

0 commit comments

Comments
 (0)