fix(script): use compile instead analyze for scripts with deps (#1912)

This commit is contained in:
Skylot
2023-06-15 21:08:51 +01:00
parent 1a642108ef
commit 6f4451364b
11 changed files with 167 additions and 61 deletions
@@ -4,15 +4,17 @@ import jadx.plugins.script.ScriptEval
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
import kotlin.script.experimental.api.ReplAnalyzerResult
import kotlin.script.experimental.api.ReplCompletionResult
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.SourceCodeCompletionVariant
import kotlin.script.experimental.api.analysisDiagnostics
import kotlin.script.experimental.api.hostConfiguration
import kotlin.script.experimental.api.renderedResultType
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.isError
import kotlin.script.experimental.jvm.util.toSourceCodePosition
const val AUTO_COMPLETE_INSERT_STR = "ABCDEF" // defined at KJvmReplCompleter.INSERTED_STRING
@@ -23,42 +25,55 @@ data class ScriptCompletionResult(
)
data class ScriptAnalyzeResult(
val success: Boolean,
val issues: List<ScriptDiagnostic>,
val renderType: String?,
val reports: List<ScriptDiagnostic>,
)
class ScriptServices(private val scriptName: String) {
private val replCompiler = KJvmReplCompilerWithIdeServices()
private val compileConf = ScriptEval().buildCompileConf()
class ScriptServices {
private val compileConf = ScriptEval.compileConf
private val replCompiler = KJvmReplCompilerWithIdeServices(
compileConf[ScriptCompilationConfiguration.hostConfiguration]
?: defaultJvmScriptingHostConfiguration,
)
fun complete(code: String, cursor: Int): ScriptCompletionResult {
val result = complete(code.toScriptSource(scriptName), cursor)
fun complete(scriptName: String, code: String, cursor: Int): ScriptCompletionResult {
val snippet = code.toScriptSource(scriptName)
val result = runBlocking {
replCompiler.complete(snippet, cursor.toSourceCodePosition(snippet), compileConf)
}
return ScriptCompletionResult(
completions = result.valueOrNull()?.toList() ?: emptyList(),
reports = result.reports,
)
}
fun analyze(code: String, cursor: Int): ScriptAnalyzeResult {
val result = analyze(code.toScriptSource(scriptName), cursor)
val analyzerResult = result.valueOrNull()
return ScriptAnalyzeResult(
issues = analyzerResult?.get(ReplAnalyzerResult.analysisDiagnostics)?.toList() ?: emptyList(),
renderType = analyzerResult?.get(ReplAnalyzerResult.renderedResultType),
reports = result.reports,
)
}
private fun complete(code: SourceCode, cursor: Int): ResultWithDiagnostics<ReplCompletionResult> {
return runBlocking {
replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConf)
}
}
private fun analyze(code: SourceCode, cursor: Int): ResultWithDiagnostics<ReplAnalyzerResult> {
return runBlocking {
replCompiler.analyze(code, cursor.toSourceCodePosition(code), compileConf)
fun analyze(scriptName: String, code: String): ScriptAnalyzeResult {
// TODO: temp solution: analyze do not work with dependencies, use compile instead
val sourceCode = code.toScriptSource(scriptName)
if (code.contains("@file:DependsOn(")) {
val result = runBlocking {
ScriptEval().compile(sourceCode)
}
return ScriptAnalyzeResult(
success = !result.isError(),
issues = result.reports,
renderType = null,
)
} else {
val result = runBlocking {
val cursor = SourceCode.Position(0, 0) // not used
replCompiler.analyze(sourceCode, cursor, compileConf)
}
val analyzerResult = result.valueOrNull()
val issues = mutableListOf<ScriptDiagnostic>()
analyzerResult?.get(ReplAnalyzerResult.analysisDiagnostics)?.let(issues::addAll)
issues.addAll(result.reports)
return ScriptAnalyzeResult(
success = !result.isError(),
issues = issues,
renderType = analyzerResult?.get(ReplAnalyzerResult.renderedResultType),
)
}
}
}
@@ -0,0 +1,45 @@
package jadx.plugins.script.ide
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class ScriptServicesTest {
@Test
fun testAnalyzeSimple() {
val name = "simple"
val script = getSampleScript(name)
val result = ScriptServices().analyze(name, script)
println(result)
assertThat(result.success).isTrue()
}
@Test
fun testAnalyzeDeps() {
val name = "test-deps"
val script = getSampleScript(name)
val result = ScriptServices().analyze(name, script)
println(result)
assertThat(result.success).isTrue()
}
@Test
fun testComplete() {
val name = "simple"
val script = getSampleScript(name)
val idx = script.indexOf("jadx.log.info")
val completePos = idx + 7 // jadx.lo| <- complete 'log'
val curScript = script.removeRange(completePos, script.indexOf("\n", idx))
val result = ScriptServices().complete(name, curScript, completePos)
println(result)
assertThat(result.completions)
.hasSize(1)
.allMatch { c -> c.text == "log" }
}
private fun getSampleScript(scriptName: String): String {
val resFile = javaClass.classLoader.getResource("samples/$scriptName.jadx.kts")
return resFile!!.readText()
}
}
@@ -0,0 +1,5 @@
val jadx = getJadxInstance()
jadx.afterLoad {
jadx.log.info { "Hello" }
}
@@ -0,0 +1,11 @@
@file:DependsOn("org.apache.commons:commons-text:1.10.0")
import org.apache.commons.text.StringEscapeUtils
val jadx = getJadxInstance()
jadx.afterLoad {
jadx.classes.forEach {
jadx.log.info { "Escaped name: ${StringEscapeUtils.escapeJava(it.fullName)}" }
}
}