Skip to content

Commit 29d2c96

Browse files
committed
Work in progress companion object main function support.
1 parent 0e68b8a commit 29d2c96

File tree

2 files changed

+54
-4
lines changed

2 files changed

+54
-4
lines changed

server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package org.javacs.kt.resolve
33
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
44
import org.jetbrains.kotlin.psi.KtFile
55
import org.jetbrains.kotlin.psi.KtNamedFunction
6+
import org.jetbrains.kotlin.psi.KtClass
7+
import org.jetbrains.kotlin.psi.KtObjectDeclaration
8+
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
9+
import org.jetbrains.kotlin.psi.KtClassOrObject
10+
import org.jetbrains.kotlin.container.topologicalSort
611
import org.javacs.kt.CompiledFile
712
import org.javacs.kt.LOG
813
import org.javacs.kt.position.range
@@ -11,9 +16,8 @@ import com.intellij.openapi.util.TextRange
1116

1217

1318
fun resolveMain(file: CompiledFile): Map<String,Any> {
14-
LOG.info("Resolving main function! yay")
15-
1619
val parsedFile = file.parse.copy() as KtFile
20+
1721
val mainFunction = findTopLevelMainFunction(parsedFile)
1822
if(null != mainFunction) {
1923
// the KtFiles name is weird. Full path. This causes the class to have full path in name as well. Correcting to top level only
@@ -22,6 +26,15 @@ fun resolveMain(file: CompiledFile): Map<String,Any> {
2226
return mapOf("mainClass" to JvmFileClassUtil.getFileClassInfoNoResolve(parsedFile).facadeClassFqName.asString(),
2327
"range" to range(file.content, mainFunction.second))
2428
}
29+
30+
val companionMain = findCompanionObjectMain(parsedFile)
31+
if(null != companionMain) {
32+
// TODO: any way we should handle the jvmname stuff here?
33+
return mapOf(
34+
"mainClass" to (companionMain.first ?: ""),
35+
"range" to range(file.content, companionMain.second)
36+
)
37+
}
2538

2639
return emptyMap()
2740
}
@@ -31,6 +44,30 @@ private fun findTopLevelMainFunction(file: KtFile): Pair<String?, TextRange>? =
3144
// TODO: any validations on arguments
3245
it is KtNamedFunction && "main" == it.name
3346
}?.let {
34-
// TODO: any better things to return?
3547
Pair(it.name, it.textRangeInParent)
3648
}
49+
50+
// TODO: can this and the previous be merged in any way? or is this approach the cleanest?
51+
// finds a top level class that contains a companion object with a main function inside
52+
private fun findCompanionObjectMain(file: KtFile): Pair<String?, TextRange>? = file.declarations.flatMap { topLevelDeclaration ->
53+
if(topLevelDeclaration is KtClass) {
54+
topLevelDeclaration.companionObjects
55+
} else {
56+
emptyList<KtObjectDeclaration>()
57+
}
58+
}.flatMap { companionObject ->
59+
companionObject.body?.children?.toList() ?: emptyList()
60+
}.mapNotNull { companionObjectInternal ->
61+
if(companionObjectInternal is KtNamedFunction && "main" == companionObjectInternal.name) { // && companionObjectInternal.annotations.any {
62+
// LOG.info("Annotation!! {}", it.name)
63+
// "JvmStatic" == it.name
64+
// }
65+
companionObjectInternal
66+
} else {
67+
null
68+
}
69+
}.firstOrNull()?.let {
70+
// a little ugly, but because of success of the above, we know that "it" has 4 layers of parent objects (child of companion object body, companion object body, companion object, outer class)
71+
// TODO: should we correct textRange start line manually? includes the JvmStatic annotation if present..
72+
Pair((it.parent.parent.parent.parent as KtClass).fqName?.toString(), it.textRange)
73+
}

server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,18 @@ class JvmNameAnnotationMainResolve : SingleFileTestFixture("resolvemain", "JvmNa
5959
}
6060
}
6161

62+
class CompanionObjectMainResolve : SingleFileTestFixture("resolvemain", "CompanionObject.kt") {
63+
@Test
64+
fun `Should resolve correct main class of main function inside companion object`() {
65+
val root = testResourcesRoot().resolve(workspaceRoot)
66+
val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString())))
67+
68+
val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get()
6269

63-
// TODO: should we support inner companion object mains?
70+
assertNotNull(commandResult)
71+
val mainInfo = commandResult as Map<String, Any>
72+
assertEquals("test.my.companion.SweetPotato", mainInfo["mainClass"])
73+
assertEquals(Range(Position(9, 8), Position(11, 9)), mainInfo["range"])
74+
assertEquals(root.toString(), mainInfo["projectRoot"])
75+
}
76+
}

0 commit comments

Comments
 (0)