Skip to content

Commit 0e68b8a

Browse files
committed
Basic resolve main-class command for use with run code lenses (and
more?)
1 parent be2f917 commit 0e68b8a

File tree

8 files changed

+146
-2
lines changed

8 files changed

+146
-2
lines changed

server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import java.nio.file.Path
1313
* and the compiler. Note that Kotlin sources are stored in SourcePath.
1414
*/
1515
class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
16-
private val workspaceRoots = mutableSetOf<Path>()
16+
val workspaceRoots = mutableSetOf<Path>()
17+
1718
private val javaSourcePath = mutableSetOf<Path>()
1819
private val buildScriptClassPath = mutableSetOf<Path>()
1920
val classPath = mutableSetOf<ClassPathEntry>()

server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import org.eclipse.lsp4j.services.LanguageClientAware
88
import org.eclipse.lsp4j.jsonrpc.messages.Either
99
import org.javacs.kt.symbols.workspaceSymbols
1010
import org.javacs.kt.command.JAVA_TO_KOTLIN_COMMAND
11+
import org.javacs.kt.command.RESOLVE_MAIN
1112
import org.javacs.kt.j2k.convertJavaToKotlin
1213
import org.javacs.kt.KotlinTextDocumentService
1314
import org.javacs.kt.position.extractRange
1415
import org.javacs.kt.util.filePath
1516
import org.javacs.kt.util.parseURI
17+
import org.javacs.kt.util.AsyncExecutor
18+
import org.javacs.kt.resolve.resolveMain
1619
import java.net.URI
1720
import java.nio.file.Paths
1821
import java.util.concurrent.CompletableFuture
@@ -30,6 +33,8 @@ class KotlinWorkspaceService(
3033
private val gson = Gson()
3134
private var languageClient: LanguageClient? = null
3235

36+
private val async = AsyncExecutor()
37+
3338
override fun connect(client: LanguageClient): Unit {
3439
languageClient = client
3540
}
@@ -53,6 +58,26 @@ class KotlinWorkspaceService(
5358
)
5459
)))))
5560
}
61+
62+
RESOLVE_MAIN -> {
63+
val fileUri = parseURI(gson.fromJson(args[0] as JsonElement, String::class.java))
64+
val filePath = Paths.get(fileUri)
65+
66+
// we find the longest one in case both the root and submodule are included
67+
val workspacePath = cp.workspaceRoots.filter {
68+
filePath.startsWith(it)
69+
}.map {
70+
it.toString()
71+
}.maxByOrNull(String::length) ?: ""
72+
73+
val compiledFile = sp.currentVersion(fileUri)
74+
75+
return async.compute {
76+
resolveMain(compiledFile) + mapOf(
77+
"projectRoot" to workspacePath
78+
)
79+
}
80+
}
5681
}
5782

5883
return CompletableFuture.completedFuture(null)
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.javacs.kt.command
22

33
const val JAVA_TO_KOTLIN_COMMAND = "convertJavaToKotlin"
4+
const val RESOLVE_MAIN = "resolveMain"
5+
46
val ALL_COMMANDS = listOf(
5-
JAVA_TO_KOTLIN_COMMAND
7+
JAVA_TO_KOTLIN_COMMAND,
8+
RESOLVE_MAIN
69
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.javacs.kt.resolve
2+
3+
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
4+
import org.jetbrains.kotlin.psi.KtFile
5+
import org.jetbrains.kotlin.psi.KtNamedFunction
6+
import org.javacs.kt.CompiledFile
7+
import org.javacs.kt.LOG
8+
import org.javacs.kt.position.range
9+
import org.javacs.kt.util.partitionAroundLast
10+
import com.intellij.openapi.util.TextRange
11+
12+
13+
fun resolveMain(file: CompiledFile): Map<String,Any> {
14+
LOG.info("Resolving main function! yay")
15+
16+
val parsedFile = file.parse.copy() as KtFile
17+
val mainFunction = findTopLevelMainFunction(parsedFile)
18+
if(null != mainFunction) {
19+
// the KtFiles name is weird. Full path. This causes the class to have full path in name as well. Correcting to top level only
20+
parsedFile.name = parsedFile.name.partitionAroundLast("/").second.substring(1)
21+
22+
return mapOf("mainClass" to JvmFileClassUtil.getFileClassInfoNoResolve(parsedFile).facadeClassFqName.asString(),
23+
"range" to range(file.content, mainFunction.second))
24+
}
25+
26+
return emptyMap()
27+
}
28+
29+
// only one allowed (so invalid syntax files will not show any main methods)
30+
private fun findTopLevelMainFunction(file: KtFile): Pair<String?, TextRange>? = file.declarations.find {
31+
// TODO: any validations on arguments
32+
it is KtNamedFunction && "main" == it.name
33+
}?.let {
34+
// TODO: any better things to return?
35+
Pair(it.name, it.textRangeInParent)
36+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.javacs.kt
2+
3+
import com.google.gson.Gson
4+
import org.eclipse.lsp4j.ExecuteCommandParams
5+
import org.eclipse.lsp4j.Position
6+
import org.eclipse.lsp4j.Range
7+
import org.junit.Test
8+
import org.junit.Assert.assertEquals
9+
import org.junit.Assert.assertNotNull
10+
import org.junit.Assert.assertNull
11+
import org.javacs.kt.command.RESOLVE_MAIN
12+
13+
class NoMainResolve : SingleFileTestFixture("resolvemain", "NoMain.kt") {
14+
@Test
15+
fun `Should not find any main class info`() {
16+
val root = testResourcesRoot().resolve(workspaceRoot)
17+
val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString(), )))
18+
19+
val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get()
20+
21+
assertNotNull(commandResult)
22+
val mainInfo = commandResult as Map<String, String>
23+
assertNull(mainInfo["mainClass"])
24+
assertEquals(root.toString(), mainInfo["projectRoot"])
25+
}
26+
}
27+
28+
29+
class SimpleMainResolve : SingleFileTestFixture("resolvemain", "Simple.kt") {
30+
@Test
31+
fun `Should resolve correct main class of simple file`() {
32+
val root = testResourcesRoot().resolve(workspaceRoot)
33+
val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString())))
34+
35+
val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get()
36+
37+
assertNotNull(commandResult)
38+
val mainInfo = commandResult as Map<String, Any>
39+
assertEquals("test.SimpleKt", mainInfo["mainClass"])
40+
assertEquals(Range(Position(2, 0), Position(4, 1)), mainInfo["range"])
41+
assertEquals(root.toString(), mainInfo["projectRoot"])
42+
}
43+
}
44+
45+
46+
class JvmNameAnnotationMainResolve : SingleFileTestFixture("resolvemain", "JvmNameAnnotation.kt") {
47+
@Test
48+
fun `Should resolve correct main class of file annotated with JvmName`() {
49+
val root = testResourcesRoot().resolve(workspaceRoot)
50+
val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString())))
51+
52+
val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get()
53+
54+
assertNotNull(commandResult)
55+
val mainInfo = commandResult as Map<String, Any>
56+
assertEquals("com.mypackage.name.Potato", mainInfo["mainClass"])
57+
assertEquals(Range(Position(5, 0), Position(7, 1)), mainInfo["range"])
58+
assertEquals(root.toString(), mainInfo["projectRoot"])
59+
}
60+
}
61+
62+
63+
// TODO: should we support inner companion object mains?
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@JvmName("Potato")
2+
package com.mypackage.name
3+
4+
val MY_CONSTANT = 1
5+
6+
fun main(args: Array<String>) {
7+
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package no.main.found.hopefully
2+
3+
fun multiplyByOne(num: Int) = num*1
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package test
2+
3+
fun main() {
4+
println("Hello!")
5+
}

0 commit comments

Comments
 (0)