From 0e68b8a906bc4d07e44856de10cd18e73d8353e7 Mon Sep 17 00:00:00 2001 From: Marie Katrine Ekeberg Date: Sat, 9 Apr 2022 23:03:56 +0200 Subject: [PATCH 1/4] Basic resolve main-class command for use with run code lenses (and more?) --- .../kotlin/org/javacs/kt/CompilerClassPath.kt | 3 +- .../org/javacs/kt/KotlinWorkspaceService.kt | 25 ++++++++ .../kotlin/org/javacs/kt/command/Commands.kt | 5 +- .../org/javacs/kt/resolve/ResolveMain.kt | 36 +++++++++++ .../org/javacs/kt/ResolveMainCommandTest.kt | 63 +++++++++++++++++++ .../resolvemain/JvmNameAnnotation.kt | 8 +++ .../src/test/resources/resolvemain/NoMain.kt | 3 + .../src/test/resources/resolvemain/Simple.kt | 5 ++ 8 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt create mode 100644 server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt create mode 100644 server/src/test/resources/resolvemain/JvmNameAnnotation.kt create mode 100644 server/src/test/resources/resolvemain/NoMain.kt create mode 100644 server/src/test/resources/resolvemain/Simple.kt diff --git a/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt b/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt index 9d3805e18..f26b47b9c 100644 --- a/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt +++ b/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt @@ -13,7 +13,8 @@ import java.nio.file.Path * and the compiler. Note that Kotlin sources are stored in SourcePath. */ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable { - private val workspaceRoots = mutableSetOf() + val workspaceRoots = mutableSetOf() + private val javaSourcePath = mutableSetOf() private val buildScriptClassPath = mutableSetOf() val classPath = mutableSetOf() diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index 85af3b1ad..704a4f3c5 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -8,11 +8,14 @@ import org.eclipse.lsp4j.services.LanguageClientAware import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.symbols.workspaceSymbols import org.javacs.kt.command.JAVA_TO_KOTLIN_COMMAND +import org.javacs.kt.command.RESOLVE_MAIN import org.javacs.kt.j2k.convertJavaToKotlin import org.javacs.kt.KotlinTextDocumentService import org.javacs.kt.position.extractRange import org.javacs.kt.util.filePath import org.javacs.kt.util.parseURI +import org.javacs.kt.util.AsyncExecutor +import org.javacs.kt.resolve.resolveMain import java.net.URI import java.nio.file.Paths import java.util.concurrent.CompletableFuture @@ -30,6 +33,8 @@ class KotlinWorkspaceService( private val gson = Gson() private var languageClient: LanguageClient? = null + private val async = AsyncExecutor() + override fun connect(client: LanguageClient): Unit { languageClient = client } @@ -53,6 +58,26 @@ class KotlinWorkspaceService( ) ))))) } + + RESOLVE_MAIN -> { + val fileUri = parseURI(gson.fromJson(args[0] as JsonElement, String::class.java)) + val filePath = Paths.get(fileUri) + + // we find the longest one in case both the root and submodule are included + val workspacePath = cp.workspaceRoots.filter { + filePath.startsWith(it) + }.map { + it.toString() + }.maxByOrNull(String::length) ?: "" + + val compiledFile = sp.currentVersion(fileUri) + + return async.compute { + resolveMain(compiledFile) + mapOf( + "projectRoot" to workspacePath + ) + } + } } return CompletableFuture.completedFuture(null) diff --git a/server/src/main/kotlin/org/javacs/kt/command/Commands.kt b/server/src/main/kotlin/org/javacs/kt/command/Commands.kt index 0b47ad7e5..a0fcf3760 100644 --- a/server/src/main/kotlin/org/javacs/kt/command/Commands.kt +++ b/server/src/main/kotlin/org/javacs/kt/command/Commands.kt @@ -1,6 +1,9 @@ package org.javacs.kt.command const val JAVA_TO_KOTLIN_COMMAND = "convertJavaToKotlin" +const val RESOLVE_MAIN = "resolveMain" + val ALL_COMMANDS = listOf( - JAVA_TO_KOTLIN_COMMAND + JAVA_TO_KOTLIN_COMMAND, + RESOLVE_MAIN ) diff --git a/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt b/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt new file mode 100644 index 000000000..d6be2d5c0 --- /dev/null +++ b/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt @@ -0,0 +1,36 @@ +package org.javacs.kt.resolve + +import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.javacs.kt.CompiledFile +import org.javacs.kt.LOG +import org.javacs.kt.position.range +import org.javacs.kt.util.partitionAroundLast +import com.intellij.openapi.util.TextRange + + +fun resolveMain(file: CompiledFile): Map { + LOG.info("Resolving main function! yay") + + val parsedFile = file.parse.copy() as KtFile + val mainFunction = findTopLevelMainFunction(parsedFile) + if(null != mainFunction) { + // the KtFiles name is weird. Full path. This causes the class to have full path in name as well. Correcting to top level only + parsedFile.name = parsedFile.name.partitionAroundLast("/").second.substring(1) + + return mapOf("mainClass" to JvmFileClassUtil.getFileClassInfoNoResolve(parsedFile).facadeClassFqName.asString(), + "range" to range(file.content, mainFunction.second)) + } + + return emptyMap() +} + +// only one allowed (so invalid syntax files will not show any main methods) +private fun findTopLevelMainFunction(file: KtFile): Pair? = file.declarations.find { + // TODO: any validations on arguments + it is KtNamedFunction && "main" == it.name +}?.let { + // TODO: any better things to return? + Pair(it.name, it.textRangeInParent) +} diff --git a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt b/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt new file mode 100644 index 000000000..241c9a3cf --- /dev/null +++ b/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt @@ -0,0 +1,63 @@ +package org.javacs.kt + +import com.google.gson.Gson +import org.eclipse.lsp4j.ExecuteCommandParams +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range +import org.junit.Test +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.javacs.kt.command.RESOLVE_MAIN + +class NoMainResolve : SingleFileTestFixture("resolvemain", "NoMain.kt") { + @Test + fun `Should not find any main class info`() { + val root = testResourcesRoot().resolve(workspaceRoot) + val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString(), ))) + + val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + + assertNotNull(commandResult) + val mainInfo = commandResult as Map + assertNull(mainInfo["mainClass"]) + assertEquals(root.toString(), mainInfo["projectRoot"]) + } +} + + +class SimpleMainResolve : SingleFileTestFixture("resolvemain", "Simple.kt") { + @Test + fun `Should resolve correct main class of simple file`() { + val root = testResourcesRoot().resolve(workspaceRoot) + val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString()))) + + val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + + assertNotNull(commandResult) + val mainInfo = commandResult as Map + assertEquals("test.SimpleKt", mainInfo["mainClass"]) + assertEquals(Range(Position(2, 0), Position(4, 1)), mainInfo["range"]) + assertEquals(root.toString(), mainInfo["projectRoot"]) + } +} + + +class JvmNameAnnotationMainResolve : SingleFileTestFixture("resolvemain", "JvmNameAnnotation.kt") { + @Test + fun `Should resolve correct main class of file annotated with JvmName`() { + val root = testResourcesRoot().resolve(workspaceRoot) + val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString()))) + + val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + + assertNotNull(commandResult) + val mainInfo = commandResult as Map + assertEquals("com.mypackage.name.Potato", mainInfo["mainClass"]) + assertEquals(Range(Position(5, 0), Position(7, 1)), mainInfo["range"]) + assertEquals(root.toString(), mainInfo["projectRoot"]) + } +} + + +// TODO: should we support inner companion object mains? diff --git a/server/src/test/resources/resolvemain/JvmNameAnnotation.kt b/server/src/test/resources/resolvemain/JvmNameAnnotation.kt new file mode 100644 index 000000000..04c03c434 --- /dev/null +++ b/server/src/test/resources/resolvemain/JvmNameAnnotation.kt @@ -0,0 +1,8 @@ +@JvmName("Potato") +package com.mypackage.name + +val MY_CONSTANT = 1 + +fun main(args: Array) { + +} diff --git a/server/src/test/resources/resolvemain/NoMain.kt b/server/src/test/resources/resolvemain/NoMain.kt new file mode 100644 index 000000000..64eb222b1 --- /dev/null +++ b/server/src/test/resources/resolvemain/NoMain.kt @@ -0,0 +1,3 @@ +package no.main.found.hopefully + +fun multiplyByOne(num: Int) = num*1 diff --git a/server/src/test/resources/resolvemain/Simple.kt b/server/src/test/resources/resolvemain/Simple.kt new file mode 100644 index 000000000..de8be317a --- /dev/null +++ b/server/src/test/resources/resolvemain/Simple.kt @@ -0,0 +1,5 @@ +package test + +fun main() { + println("Hello!") +} From 29d2c965d6085f6d60a0377400e6bcc116bea95f Mon Sep 17 00:00:00 2001 From: Marie Katrine Ekeberg Date: Sun, 10 Apr 2022 15:41:58 +0200 Subject: [PATCH 2/4] Work in progress companion object main function support. --- .../org/javacs/kt/resolve/ResolveMain.kt | 43 +++++++++++++++++-- .../org/javacs/kt/ResolveMainCommandTest.kt | 15 ++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt b/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt index d6be2d5c0..91aac4e15 100644 --- a/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt +++ b/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt @@ -3,6 +3,11 @@ package org.javacs.kt.resolve import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.container.topologicalSort import org.javacs.kt.CompiledFile import org.javacs.kt.LOG import org.javacs.kt.position.range @@ -11,9 +16,8 @@ import com.intellij.openapi.util.TextRange fun resolveMain(file: CompiledFile): Map { - LOG.info("Resolving main function! yay") - val parsedFile = file.parse.copy() as KtFile + val mainFunction = findTopLevelMainFunction(parsedFile) if(null != mainFunction) { // 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 { return mapOf("mainClass" to JvmFileClassUtil.getFileClassInfoNoResolve(parsedFile).facadeClassFqName.asString(), "range" to range(file.content, mainFunction.second)) } + + val companionMain = findCompanionObjectMain(parsedFile) + if(null != companionMain) { + // TODO: any way we should handle the jvmname stuff here? + return mapOf( + "mainClass" to (companionMain.first ?: ""), + "range" to range(file.content, companionMain.second) + ) + } return emptyMap() } @@ -31,6 +44,30 @@ private fun findTopLevelMainFunction(file: KtFile): Pair? = // TODO: any validations on arguments it is KtNamedFunction && "main" == it.name }?.let { - // TODO: any better things to return? Pair(it.name, it.textRangeInParent) } + +// TODO: can this and the previous be merged in any way? or is this approach the cleanest? +// finds a top level class that contains a companion object with a main function inside +private fun findCompanionObjectMain(file: KtFile): Pair? = file.declarations.flatMap { topLevelDeclaration -> + if(topLevelDeclaration is KtClass) { + topLevelDeclaration.companionObjects + } else { + emptyList() + } +}.flatMap { companionObject -> + companionObject.body?.children?.toList() ?: emptyList() +}.mapNotNull { companionObjectInternal -> + if(companionObjectInternal is KtNamedFunction && "main" == companionObjectInternal.name) { // && companionObjectInternal.annotations.any { + // LOG.info("Annotation!! {}", it.name) + // "JvmStatic" == it.name + // } + companionObjectInternal + } else { + null + } +}.firstOrNull()?.let { + // 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) + // TODO: should we correct textRange start line manually? includes the JvmStatic annotation if present.. + Pair((it.parent.parent.parent.parent as KtClass).fqName?.toString(), it.textRange) +} diff --git a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt b/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt index 241c9a3cf..68ab2a617 100644 --- a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt @@ -59,5 +59,18 @@ class JvmNameAnnotationMainResolve : SingleFileTestFixture("resolvemain", "JvmNa } } +class CompanionObjectMainResolve : SingleFileTestFixture("resolvemain", "CompanionObject.kt") { + @Test + fun `Should resolve correct main class of main function inside companion object`() { + val root = testResourcesRoot().resolve(workspaceRoot) + val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString()))) + + val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() -// TODO: should we support inner companion object mains? + assertNotNull(commandResult) + val mainInfo = commandResult as Map + assertEquals("test.my.companion.SweetPotato", mainInfo["mainClass"]) + assertEquals(Range(Position(9, 8), Position(11, 9)), mainInfo["range"]) + assertEquals(root.toString(), mainInfo["projectRoot"]) + } +} From cd015765fd6fecd0aba02e87e02dac4cc76c00b4 Mon Sep 17 00:00:00 2001 From: Marie Katrine Ekeberg Date: Sun, 10 Apr 2022 16:03:29 +0200 Subject: [PATCH 3/4] Clean up and finalized companion object main resolving. Also adding test resource that was not added earlier. --- .../kotlin/org/javacs/kt/resolve/ResolveMain.kt | 15 ++------------- .../org/javacs/kt/ResolveMainCommandTest.kt | 2 +- .../test/resources/resolvemain/CompanionObject.kt | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 server/src/test/resources/resolvemain/CompanionObject.kt diff --git a/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt b/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt index 91aac4e15..b6282eae2 100644 --- a/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt +++ b/server/src/main/kotlin/org/javacs/kt/resolve/ResolveMain.kt @@ -5,11 +5,7 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtObjectDeclaration -import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.container.topologicalSort import org.javacs.kt.CompiledFile -import org.javacs.kt.LOG import org.javacs.kt.position.range import org.javacs.kt.util.partitionAroundLast import com.intellij.openapi.util.TextRange @@ -29,7 +25,6 @@ fun resolveMain(file: CompiledFile): Map { val companionMain = findCompanionObjectMain(parsedFile) if(null != companionMain) { - // TODO: any way we should handle the jvmname stuff here? return mapOf( "mainClass" to (companionMain.first ?: ""), "range" to range(file.content, companionMain.second) @@ -39,15 +34,13 @@ fun resolveMain(file: CompiledFile): Map { return emptyMap() } -// only one allowed (so invalid syntax files will not show any main methods) +// only one main method allowed top level in a file (so invalid syntax files will not show any main methods) private fun findTopLevelMainFunction(file: KtFile): Pair? = file.declarations.find { - // TODO: any validations on arguments it is KtNamedFunction && "main" == it.name }?.let { Pair(it.name, it.textRangeInParent) } -// TODO: can this and the previous be merged in any way? or is this approach the cleanest? // finds a top level class that contains a companion object with a main function inside private fun findCompanionObjectMain(file: KtFile): Pair? = file.declarations.flatMap { topLevelDeclaration -> if(topLevelDeclaration is KtClass) { @@ -58,16 +51,12 @@ private fun findCompanionObjectMain(file: KtFile): Pair? = f }.flatMap { companionObject -> companionObject.body?.children?.toList() ?: emptyList() }.mapNotNull { companionObjectInternal -> - if(companionObjectInternal is KtNamedFunction && "main" == companionObjectInternal.name) { // && companionObjectInternal.annotations.any { - // LOG.info("Annotation!! {}", it.name) - // "JvmStatic" == it.name - // } + if(companionObjectInternal is KtNamedFunction && "main" == companionObjectInternal.name && companionObjectInternal.text.startsWith("@JvmStatic")) { companionObjectInternal } else { null } }.firstOrNull()?.let { // 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) - // TODO: should we correct textRange start line manually? includes the JvmStatic annotation if present.. Pair((it.parent.parent.parent.parent as KtClass).fqName?.toString(), it.textRange) } diff --git a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt b/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt index 68ab2a617..e9997843d 100644 --- a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt @@ -70,7 +70,7 @@ class CompanionObjectMainResolve : SingleFileTestFixture("resolvemain", "Compani assertNotNull(commandResult) val mainInfo = commandResult as Map assertEquals("test.my.companion.SweetPotato", mainInfo["mainClass"]) - assertEquals(Range(Position(9, 8), Position(11, 9)), mainInfo["range"]) + assertEquals(Range(Position(8, 8), Position(11, 9)), mainInfo["range"]) assertEquals(root.toString(), mainInfo["projectRoot"]) } } diff --git a/server/src/test/resources/resolvemain/CompanionObject.kt b/server/src/test/resources/resolvemain/CompanionObject.kt new file mode 100644 index 000000000..b9a95def1 --- /dev/null +++ b/server/src/test/resources/resolvemain/CompanionObject.kt @@ -0,0 +1,14 @@ +package test.my.companion + +val SOME_GLOBAL_CONSTANT = 42 + +fun multiplyByOne(num: Int) = num*1 + +class SweetPotato { + companion object { + @JvmStatic + fun main() { + println("42 multiplied by 1: ${multiplyByOne(42)}") + } + } +} From d9d7b6a36b971a2096b530687fd47499d6e359bd Mon Sep 17 00:00:00 2001 From: Marie Katrine Ekeberg Date: Mon, 25 Apr 2022 17:19:25 +0200 Subject: [PATCH 4/4] Moved main resolve logic to protocol extensions instead of commands --- .../org/javacs/kt/KotlinLanguageServer.kt | 2 +- .../kt/KotlinProtocolExtensionService.kt | 23 ++++++++++++- .../org/javacs/kt/KotlinProtocolExtensions.kt | 3 ++ .../org/javacs/kt/KotlinWorkspaceService.kt | 26 +------------- .../kotlin/org/javacs/kt/command/Commands.kt | 2 -- ...eMainCommandTest.kt => ResolveMainTest.kt} | 34 +++++++++---------- 6 files changed, 44 insertions(+), 46 deletions(-) rename server/src/test/kotlin/org/javacs/kt/{ResolveMainCommandTest.kt => ResolveMainTest.kt} (59%) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index 7f2017359..b3a417bd7 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -30,7 +30,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable { private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider, classPath) private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath, textDocuments, config) - private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider, classPath) + private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider, classPath, sourcePath) private lateinit var client: LanguageClient diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensionService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensionService.kt index cba9d5d6d..4fa37acf7 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensionService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensionService.kt @@ -3,11 +3,14 @@ package org.javacs.kt import org.eclipse.lsp4j.* import org.javacs.kt.util.AsyncExecutor import org.javacs.kt.util.parseURI +import org.javacs.kt.resolve.resolveMain import java.util.concurrent.CompletableFuture +import java.nio.file.Paths class KotlinProtocolExtensionService( private val uriContentProvider: URIContentProvider, - private val cp: CompilerClassPath + private val cp: CompilerClassPath, + private val sp: SourcePath ) : KotlinProtocolExtensions { private val async = AsyncExecutor() @@ -18,4 +21,22 @@ class KotlinProtocolExtensionService( override fun buildOutputLocation(): CompletableFuture = async.compute { cp.outputDirectory.absolutePath } + + override fun mainClass(textDocument: TextDocumentIdentifier): CompletableFuture> = async.compute { + val fileUri = parseURI(textDocument.uri) + val filePath = Paths.get(fileUri) + + // we find the longest one in case both the root and submodule are included + val workspacePath = cp.workspaceRoots.filter { + filePath.startsWith(it) + }.map { + it.toString() + }.maxByOrNull(String::length) ?: "" + + val compiledFile = sp.currentVersion(fileUri) + + resolveMain(compiledFile) + mapOf( + "projectRoot" to workspacePath + ) + } } diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensions.kt b/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensions.kt index b6338eaf5..808ba0dce 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensions.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensions.kt @@ -12,4 +12,7 @@ interface KotlinProtocolExtensions { @JsonRequest fun buildOutputLocation(): CompletableFuture + + @JsonRequest + fun mainClass(textDocument: TextDocumentIdentifier): CompletableFuture> } diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index 704a4f3c5..470853cc4 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -8,13 +8,11 @@ import org.eclipse.lsp4j.services.LanguageClientAware import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.symbols.workspaceSymbols import org.javacs.kt.command.JAVA_TO_KOTLIN_COMMAND -import org.javacs.kt.command.RESOLVE_MAIN import org.javacs.kt.j2k.convertJavaToKotlin import org.javacs.kt.KotlinTextDocumentService import org.javacs.kt.position.extractRange import org.javacs.kt.util.filePath import org.javacs.kt.util.parseURI -import org.javacs.kt.util.AsyncExecutor import org.javacs.kt.resolve.resolveMain import java.net.URI import java.nio.file.Paths @@ -32,9 +30,7 @@ class KotlinWorkspaceService( ) : WorkspaceService, LanguageClientAware { private val gson = Gson() private var languageClient: LanguageClient? = null - - private val async = AsyncExecutor() - + override fun connect(client: LanguageClient): Unit { languageClient = client } @@ -58,26 +54,6 @@ class KotlinWorkspaceService( ) ))))) } - - RESOLVE_MAIN -> { - val fileUri = parseURI(gson.fromJson(args[0] as JsonElement, String::class.java)) - val filePath = Paths.get(fileUri) - - // we find the longest one in case both the root and submodule are included - val workspacePath = cp.workspaceRoots.filter { - filePath.startsWith(it) - }.map { - it.toString() - }.maxByOrNull(String::length) ?: "" - - val compiledFile = sp.currentVersion(fileUri) - - return async.compute { - resolveMain(compiledFile) + mapOf( - "projectRoot" to workspacePath - ) - } - } } return CompletableFuture.completedFuture(null) diff --git a/server/src/main/kotlin/org/javacs/kt/command/Commands.kt b/server/src/main/kotlin/org/javacs/kt/command/Commands.kt index a0fcf3760..6d7feb7e0 100644 --- a/server/src/main/kotlin/org/javacs/kt/command/Commands.kt +++ b/server/src/main/kotlin/org/javacs/kt/command/Commands.kt @@ -1,9 +1,7 @@ package org.javacs.kt.command const val JAVA_TO_KOTLIN_COMMAND = "convertJavaToKotlin" -const val RESOLVE_MAIN = "resolveMain" val ALL_COMMANDS = listOf( JAVA_TO_KOTLIN_COMMAND, - RESOLVE_MAIN ) diff --git a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt b/server/src/test/kotlin/org/javacs/kt/ResolveMainTest.kt similarity index 59% rename from server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt rename to server/src/test/kotlin/org/javacs/kt/ResolveMainTest.kt index e9997843d..65ca7d5fc 100644 --- a/server/src/test/kotlin/org/javacs/kt/ResolveMainCommandTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/ResolveMainTest.kt @@ -4,22 +4,22 @@ import com.google.gson.Gson import org.eclipse.lsp4j.ExecuteCommandParams import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range +import org.eclipse.lsp4j.TextDocumentIdentifier import org.junit.Test import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull -import org.javacs.kt.command.RESOLVE_MAIN class NoMainResolve : SingleFileTestFixture("resolvemain", "NoMain.kt") { @Test fun `Should not find any main class info`() { val root = testResourcesRoot().resolve(workspaceRoot) - val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString(), ))) + val fileUri = root.resolve(file).toUri().toString() - val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + val result = languageServer.getProtocolExtensionService().mainClass(TextDocumentIdentifier(fileUri)).get() - assertNotNull(commandResult) - val mainInfo = commandResult as Map + assertNotNull(result) + val mainInfo = result as Map assertNull(mainInfo["mainClass"]) assertEquals(root.toString(), mainInfo["projectRoot"]) } @@ -30,12 +30,12 @@ class SimpleMainResolve : SingleFileTestFixture("resolvemain", "Simple.kt") { @Test fun `Should resolve correct main class of simple file`() { val root = testResourcesRoot().resolve(workspaceRoot) - val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString()))) + val fileUri = root.resolve(file).toUri().toString() - val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + val result = languageServer.getProtocolExtensionService().mainClass(TextDocumentIdentifier(fileUri)).get() - assertNotNull(commandResult) - val mainInfo = commandResult as Map + assertNotNull(result) + val mainInfo = result as Map assertEquals("test.SimpleKt", mainInfo["mainClass"]) assertEquals(Range(Position(2, 0), Position(4, 1)), mainInfo["range"]) assertEquals(root.toString(), mainInfo["projectRoot"]) @@ -47,12 +47,12 @@ class JvmNameAnnotationMainResolve : SingleFileTestFixture("resolvemain", "JvmNa @Test fun `Should resolve correct main class of file annotated with JvmName`() { val root = testResourcesRoot().resolve(workspaceRoot) - val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString()))) + val fileUri = root.resolve(file).toUri().toString() - val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + val result = languageServer.getProtocolExtensionService().mainClass(TextDocumentIdentifier(fileUri)).get() - assertNotNull(commandResult) - val mainInfo = commandResult as Map + assertNotNull(result) + val mainInfo = result as Map assertEquals("com.mypackage.name.Potato", mainInfo["mainClass"]) assertEquals(Range(Position(5, 0), Position(7, 1)), mainInfo["range"]) assertEquals(root.toString(), mainInfo["projectRoot"]) @@ -63,12 +63,12 @@ class CompanionObjectMainResolve : SingleFileTestFixture("resolvemain", "Compani @Test fun `Should resolve correct main class of main function inside companion object`() { val root = testResourcesRoot().resolve(workspaceRoot) - val executeCommandParams = ExecuteCommandParams(RESOLVE_MAIN, listOf(Gson().toJsonTree(root.resolve(file).toUri().toString()))) + val fileUri = root.resolve(file).toUri().toString() - val commandResult = languageServer.workspaceService.executeCommand(executeCommandParams).get() + val result = languageServer.getProtocolExtensionService().mainClass(TextDocumentIdentifier(fileUri)).get() - assertNotNull(commandResult) - val mainInfo = commandResult as Map + assertNotNull(result) + val mainInfo = result as Map assertEquals("test.my.companion.SweetPotato", mainInfo["mainClass"]) assertEquals(Range(Position(8, 8), Position(11, 9)), mainInfo["range"]) assertEquals(root.toString(), mainInfo["projectRoot"])