From 2c3b42f1989ea40d133aff8eba00044d18bc36d7 Mon Sep 17 00:00:00 2001 From: "Evgeniy.Zhelenskiy" Date: Tue, 23 May 2023 21:19:44 +0200 Subject: [PATCH 1/6] Replace compiler internal API with compiler main function call besides completion --- .../kotlin/component/KotlinEnvironment.kt | 26 +-- .../server/compiler/components/CliUtils.kt | 127 ++++++++++ .../compiler/components/CompletionProvider.kt | 15 +- .../compiler/components/ErrorAnalyzer.kt | 30 +-- .../compiler/components/KotlinCompiler.kt | 193 +++++++-------- .../components/KotlinToJSTranslator.kt | 221 ++++++++++-------- .../controllers/CompilerRestController.kt | 4 +- .../compiler/server/model/ErrorDescriptor.kt | 2 +- .../compiler/server/model/ExecutionResult.kt | 54 ++++- .../compiler/server/model/ProgramOutput.kt | 2 +- .../com/compiler/server/model/TextInterval.kt | 7 +- .../server/service/KotlinProjectExecutor.kt | 70 +++--- .../server/AfterBackendDiagnosticsTest.kt | 20 ++ .../server/CommandLineArgumentsTest.kt | 4 +- .../com/compiler/server/HighlightTest.kt | 8 +- .../com/compiler/server/JvmRunnerTest.kt | 24 +- .../compiler/server/ResourceCompileTest.kt | 8 +- .../compiler/server/base/BaseExecutorTest.kt | 4 +- .../compiler/server/base/DiagnosticUtils.kt | 21 +- .../com/compiler/server/base/TestUtils.kt | 28 +-- .../server/generator/TestProjectRunner.kt | 18 +- 21 files changed, 543 insertions(+), 343 deletions(-) create mode 100644 src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt create mode 100644 src/test/kotlin/com/compiler/server/AfterBackendDiagnosticsTest.kt diff --git a/common/src/main/kotlin/component/KotlinEnvironment.kt b/common/src/main/kotlin/component/KotlinEnvironment.kt index 301fc82fa..edd57f459 100644 --- a/common/src/main/kotlin/component/KotlinEnvironment.kt +++ b/common/src/main/kotlin/component/KotlinEnvironment.kt @@ -4,11 +4,7 @@ import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments -import org.jetbrains.kotlin.cli.common.createPhaseConfig -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation import org.jetbrains.kotlin.cli.common.messages.MessageCollector -import org.jetbrains.kotlin.cli.js.K2JsIrCompiler import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots @@ -18,7 +14,6 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.languageVersionSettings -import org.jetbrains.kotlin.ir.backend.js.jsPhases import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil @@ -39,7 +34,7 @@ class KotlinEnvironment( * See [org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments] and * [org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments] for list of possible flags */ - private val additionalCompilerArguments: List = listOf( + val additionalCompilerArguments: List = listOf( "-opt-in=kotlin.ExperimentalStdlibApi", "-opt-in=kotlin.time.ExperimentalTime", "-opt-in=kotlin.RequiresOptIn", @@ -47,7 +42,6 @@ class KotlinEnvironment( "-opt-in=kotlin.contracts.ExperimentalContracts", "-opt-in=kotlin.experimental.ExperimentalTypeInference", "-Xcontext-receivers", - "-XXLanguage:+RangeUntilOperator" ) } @@ -81,22 +75,6 @@ class KotlinEnvironment( put(JSConfigurationKeys.WASM_ENABLE_ASSERTS, false) } - private val messageCollector = object : MessageCollector { - override fun clear() {} - override fun hasErrors(): Boolean { - return false - } - - override fun report( - severity: CompilerMessageSeverity, - message: String, - location: CompilerMessageSourceLocation? - ) { - } - } - - val jsIrPhaseConfig = createPhaseConfig(jsPhases, K2JsIrCompiler().createArguments(), messageCollector) - private val environment = KotlinCoreEnvironment.createForProduction( parentDisposable = Disposer.newDisposable(), configuration = configuration.copy(), @@ -128,4 +106,4 @@ class KotlinEnvironment( } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt new file mode 100644 index 000000000..9df53d536 --- /dev/null +++ b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt @@ -0,0 +1,127 @@ +package com.compiler.server.compiler.components + +import com.compiler.server.model.CompilerDiagnostics +import com.compiler.server.model.ErrorDescriptor +import com.compiler.server.model.ProjectSeveriry +import com.compiler.server.model.TextInterval +import org.jetbrains.kotlin.cli.common.CLICompiler +import org.jetbrains.kotlin.cli.common.CLITool +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer +import org.jetbrains.kotlin.psi.KtFile +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* +import kotlin.io.path.* + + +sealed class CompilationResult { + abstract val compilerDiagnostics: CompilerDiagnostics + + fun flatMap(action: (T) -> CompilationResult): CompilationResult = when (this) { + is Compiled -> { + val innerResult = action(result) + val newDiagnostics = (compilerDiagnostics.map.keys + innerResult.compilerDiagnostics.map.keys).associateWith { + val l1 = compilerDiagnostics.map[it] + val l2 = innerResult.compilerDiagnostics.map[it] + if (l1 != null && l2 != null) l1 + l2 else (l1 ?: l2)!! + }.let(::CompilerDiagnostics) + when (innerResult) { + is Compiled -> innerResult.copy(compilerDiagnostics = newDiagnostics) + is NotCompiled -> innerResult.copy(compilerDiagnostics = newDiagnostics) + } + } + is NotCompiled -> this + } + fun map(action: (T) -> R): CompilationResult = when (this) { + is Compiled -> Compiled(compilerDiagnostics, action(result)) + is NotCompiled -> this + } +} + +data class Compiled(override val compilerDiagnostics: CompilerDiagnostics, val result: T) : CompilationResult() + +data class NotCompiled(override val compilerDiagnostics: CompilerDiagnostics) : CompilationResult() + +fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List, arguments: List): CompilationResult = tryCompilation(inputDirectory, inputFiles, arguments) {} + +fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List, arguments: List, onSuccess: () -> T): CompilationResult { + fun Path.outputFilePathString() = inputDirectory.relativize(this).pathString + + val diagnosticsMap = mutableMapOf>().apply { + inputFiles.forEach { put(it.outputFilePathString(), mutableListOf()) } + } + val defaultFileName = inputFiles.singleOrNull()?.outputFilePathString() ?: "" + val exitCode = CLITool.doMainNoExit(this, arguments.toTypedArray(), object : MessageRenderer { + override fun renderPreamble(): String = "" + + override fun render( + severity: CompilerMessageSeverity, + message: String, + location: CompilerMessageSourceLocation? + ): String { + val textInterval = location?.let { + TextInterval( + start = TextInterval.TextPosition(location.line, location.column), + end = TextInterval.TextPosition(location.lineEnd, location.columnEnd) + ) + } + val messageSeverity: ProjectSeveriry = when (severity) { + EXCEPTION, ERROR -> ProjectSeveriry.ERROR + STRONG_WARNING, WARNING -> ProjectSeveriry.WARNING + INFO, LOGGING, OUTPUT -> ProjectSeveriry.INFO + } + val errorFilePath = location?.path?.let(::Path)?.outputFilePathString() ?: defaultFileName + val errorDescriptor = ErrorDescriptor(textInterval, message, messageSeverity, className = "WARNING".takeIf { messageSeverity == ProjectSeveriry.WARNING }) + diagnosticsMap.getOrPut(errorFilePath) { mutableListOf() }.add(errorDescriptor) + return "" + } + + override fun renderUsage(usage: String): String = + render(STRONG_WARNING, usage, null) + + override fun renderConclusion(): String = "" + + override fun getName(): String = "Redirector" + }) + val diagnostics = CompilerDiagnostics(diagnosticsMap) + return when { + diagnostics.any { it.severity == ProjectSeveriry.ERROR } -> NotCompiled(diagnostics) + exitCode.code != 0 -> ErrorDescriptor( + severity = ProjectSeveriry.ERROR, + message = "Compiler finished with non-null exit code ${exitCode.code}: ${exitCode.name}", + interval = null + ).let { NotCompiled(CompilerDiagnostics(mapOf(defaultFileName to listOf(it)))) } + + else -> Compiled(result = onSuccess(), compilerDiagnostics = diagnostics) + } +} + +@OptIn(ExperimentalPathApi::class) +fun usingTempDirectory(action: (path: Path) -> T): T { + val path = getTempDirectory() + path.createDirectories() + return try { + action(path) + } finally { + path.deleteRecursively() + } +} + +private fun getTempDirectory(): Path { + val dir = System.getProperty("java.io.tmpdir") + val sessionId = UUID.randomUUID().toString().replace("-", "") + return Paths.get(dir, sessionId) +} + +fun List.writeToIoFiles(inputDir: Path): List { + val ioFiles = map { inputDir / it.name } + for ((ioFile, ktFile) in ioFiles zip this) { + ioFile.writeText(ktFile.text) + } + return ioFiles +} + +val PATH_SEPARATOR: String = java.io.File.pathSeparator diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CompletionProvider.kt b/src/main/kotlin/com/compiler/server/compiler/components/CompletionProvider.kt index 121f0530d..dad3094e1 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CompletionProvider.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CompletionProvider.kt @@ -3,7 +3,7 @@ package com.compiler.server.compiler.components import com.compiler.server.compiler.KotlinFile import com.compiler.server.compiler.KotlinResolutionFacade import com.compiler.server.model.Analysis -import com.compiler.server.model.ErrorDescriptor +import com.compiler.server.model.CompilerDiagnostics import com.compiler.server.model.ProjectType import com.intellij.psi.PsiElement import com.intellij.psi.tree.TokenSet @@ -72,11 +72,11 @@ class CompletionProvider( importVariants(file, prefix, errors, line, character, projectType) } else emptyList() descriptorInfo.descriptors.toMutableList().apply { - sortWith(Comparator { a, b -> + sortWith { a, b -> val (a1, a2) = a.presentableName() val (b1, b2) = b.presentableName() ("$a1$a2").compareTo("$b1$b2", true) - }) + } }.mapNotNull { descriptor -> completionVariantFor(prefix, descriptor, element) } + keywordsCompletionVariants(KtTokens.KEYWORDS, prefix) + keywordsCompletionVariants(KtTokens.SOFT_KEYWORDS, prefix) + @@ -113,15 +113,16 @@ class CompletionProvider( private fun importVariants( file: KotlinFile, prefix: String, - errors: Map>, + compilerDiagnostics: CompilerDiagnostics, line: Int, character: Int, projectType: ProjectType ): List { val importCompletionVariants = indexationProvider.getClassesByName(prefix, projectType) ?.map { it.toCompletion() } ?: emptyList() - val currentErrors = errors[file.kotlinFile.name]?.filter { - it.interval.start.line == line && + val currentErrors = compilerDiagnostics.map[file.kotlinFile.name]?.filter { + it.interval != null && + it.interval.start.line == line && it.interval.start.ch <= character && it.interval.end.line == line && it.interval.end.ch >= character && @@ -257,7 +258,7 @@ class CompletionProvider( // This code is a fragment of org.jetbrains.kotlin.idea.completion.CompletionSession from Kotlin IDE Plugin // with a few simplifications which were possible because webdemo has very restricted environment (and well, - // because requirements on compeltion' quality in web-demo are lower) + // because requirements on completion' quality in web-demo are lower) private inner class VisibilityFilter( private val inDescriptor: DeclarationDescriptor, private val bindingContext: BindingContext, diff --git a/src/main/kotlin/com/compiler/server/compiler/components/ErrorAnalyzer.kt b/src/main/kotlin/com/compiler/server/compiler/components/ErrorAnalyzer.kt index 72dba4af1..6b94c0505 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/ErrorAnalyzer.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/ErrorAnalyzer.kt @@ -9,9 +9,9 @@ import com.intellij.psi.PsiFile import component.KotlinEnvironment import model.Completion import org.jetbrains.kotlin.analyzer.AnalysisResult -import org.jetbrains.kotlin.cli.js.klib.TopDownAnalyzerFacadeForWasmJs import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.js.klib.TopDownAnalyzerFacadeForJSIR +import org.jetbrains.kotlin.cli.js.klib.TopDownAnalyzerFacadeForWasmJs import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.incremental.components.InlineConstTracker import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.ir.backend.js.MainModule import org.jetbrains.kotlin.ir.backend.js.ModulesStructure -import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices import org.jetbrains.kotlin.name.Name @@ -60,7 +59,7 @@ class ErrorAnalyzer( projectType: ProjectType ): ErrorsAndAnalysis { val analysis = when { - projectType.isJvmRelated() ->analysisOf(files, coreEnvironment) + projectType.isJvmRelated() -> analysisOf(files, coreEnvironment) projectType.isJsRelated() -> analyzeFileForJs(files, coreEnvironment) projectType == ProjectType.WASM -> analyzeFileForWasm(files, coreEnvironment) else -> throw IllegalArgumentException("Unknown platform: $projectType") @@ -68,7 +67,7 @@ class ErrorAnalyzer( return ErrorsAndAnalysis( errorsFrom( analysis.analysisResult.bindingContext.diagnostics.all(), - files.associate { it.name to anylizeErrorsFrom(it, projectType) }, + CompilerDiagnostics(files.associate { it.name to analyzeErrorsFrom(it, projectType) }), projectType ), analysis @@ -233,24 +232,13 @@ class ErrorAnalyzer( fun errorsFrom( diagnostics: Collection, - errors: Map>, + compilerDiagnostics: CompilerDiagnostics, projectType: ProjectType - ): Map> { - return (errors and errorsFrom(diagnostics, projectType)).map { (fileName, errors) -> - fileName to errors.sortedWith { o1, o2 -> - val line = o1.interval.start.line.compareTo(o2.interval.start.line) - when (line) { - 0 -> o1.interval.start.ch.compareTo(o2.interval.start.ch) - else -> line - } - } - }.toMap() - } - - fun isOnlyWarnings(errors: Map>) = - errors.none { it.value.any { error -> error.severity == ProjectSeveriry.ERROR } } + ): CompilerDiagnostics = (compilerDiagnostics.map and errorsFrom(diagnostics, projectType)).map { (fileName, errors) -> + fileName to errors.sortedWith(Comparator.comparing({ it.interval?.start }, nullsFirst())) + }.toMap().let(::CompilerDiagnostics) - private fun anylizeErrorsFrom(file: PsiFile, projectType: ProjectType): List { + private fun analyzeErrorsFrom(file: PsiFile, projectType: ProjectType): List { class Visitor : PsiElementVisitor() { val errors = mutableListOf() override fun visitElement(element: PsiElement) { @@ -362,4 +350,4 @@ class ErrorAnalyzer( } } -data class ErrorsAndAnalysis(val errors: Map>, val analysis: Analysis) \ No newline at end of file +data class ErrorsAndAnalysis(val compilerDiagnostics: CompilerDiagnostics, val analysis: Analysis) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt index 3b529d9b1..573231c7a 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt @@ -1,37 +1,31 @@ package com.compiler.server.compiler.components import com.compiler.server.executor.CommandLineArgument -import com.compiler.server.executor.ExecutorMessages import com.compiler.server.executor.JavaExecutor -import com.compiler.server.model.* +import com.compiler.server.model.ExecutionResult +import com.compiler.server.model.OutputDirectory import com.compiler.server.model.bean.LibrariesFile +import com.compiler.server.model.toExceptionDescriptor import component.KotlinEnvironment import executors.JUnitExecutors import executors.JavaRunnerExecutor -import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig -import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory -import org.jetbrains.kotlin.backend.jvm.jvmPhases -import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.cli.jvm.compiler.findMainClass -import org.jetbrains.kotlin.codegen.ClassBuilderFactories -import org.jetbrains.kotlin.codegen.CodegenFactory -import org.jetbrains.kotlin.codegen.DefaultCodegenFactory -import org.jetbrains.kotlin.codegen.KotlinCodegenFacade -import org.jetbrains.kotlin.codegen.state.GenerationState -import org.jetbrains.kotlin.config.JVMConfigurationKeys -import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassReader.* +import org.jetbrains.org.objectweb.asm.ClassVisitor +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes.* import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import java.io.File +import java.nio.file.FileVisitResult import java.nio.file.Files -import java.nio.file.Paths -import java.util.* +import java.nio.file.Path +import kotlin.io.path.* @Component class KotlinCompiler( - private val errorAnalyzer: ErrorAnalyzer, private val kotlinEnvironment: KotlinEnvironment, private val javaExecutor: JavaExecutor, private val librariesFile: LibrariesFile, @@ -39,113 +33,121 @@ class KotlinCompiler( ) { private val policyFile = File(policyFileName) - companion object { - private val PATH_SEPARATOR = System.getProperty("path.separator") ?: ":" - } - - class Compiled(val files: Map = emptyMap(), val mainClass: String? = null) + data class JvmClasses( + val files: Map = emptyMap(), + val mainClasses: Set = emptySet() + ) - fun run(files: List, coreEnvironment: KotlinCoreEnvironment, args: String): ExecutionResult { - return execute(files, coreEnvironment) { output, compiled -> + fun run(files: List, args: String): ExecutionResult { + return execute(files) { output, compiled -> val mainClass = JavaRunnerExecutor::class.java.name - val arguments = listOfNotNull(compiled.mainClass) + args.split(" ") + val compiledMainClass = when (compiled.mainClasses.size) { + 0 -> return@execute ExecutionResult( + exception = IllegalArgumentException("No main method found in project").toExceptionDescriptor() + ) + + 1 -> compiled.mainClasses.single() + else -> return@execute ExecutionResult( + exception = IllegalArgumentException( + "Multiple classes in project contain main methods found: ${compiled.mainClasses.joinToString()}" + ).toExceptionDescriptor() + ) + } + val arguments = listOfNotNull(compiledMainClass) + args.split(" ") javaExecutor.execute(argsFrom(mainClass, output, arguments)) .asExecutionResult() } } - fun test(files: List, coreEnvironment: KotlinCoreEnvironment): ExecutionResult { - return execute(files, coreEnvironment) { output, _ -> + fun test(files: List): ExecutionResult { + return execute(files) { output, _ -> val mainClass = JUnitExecutors::class.java.name javaExecutor.execute(argsFrom(mainClass, output, listOf(output.path.toString()))) .asJUnitExecutionResult() } } - private fun compile(files: List, analysis: Analysis, coreEnvironment: KotlinCoreEnvironment): Compiled { - val generationState = generationStateFor(files, analysis, coreEnvironment) - KotlinCodegenFacade.compileCorrectFiles(generationState) - return Compiled( - files = generationState.factory.asList().associate { it.relativePath to it.asByteArray() }, - mainClass = findMainClass( - generationState.bindingContext, - LanguageVersionSettingsImpl.DEFAULT, - files - )?.asString() - ) + @OptIn(ExperimentalPathApi::class) + fun compile(files: List): CompilationResult = usingTempDirectory { inputDir -> + val ioFiles = files.writeToIoFiles(inputDir) + usingTempDirectory { outputDir -> + val arguments = ioFiles.map { it.absolutePathString() } + KotlinEnvironment.additionalCompilerArguments + listOf( + "-cp", kotlinEnvironment.classpath.joinToString(PATH_SEPARATOR) { it.absolutePath }, + "-module-name", "web-module", + "-no-stdlib", "-no-reflect", + "-d", outputDir.absolutePathString() + ) + K2JVMCompiler().tryCompilation(inputDir, ioFiles, arguments) { + val outputFiles = buildMap { + outputDir.visitFileTree { + onVisitFile { file, _ -> + put(file.relativeTo(outputDir).pathString, file.readBytes()) + FileVisitResult.CONTINUE + } + } + } + val mainClasses = findMainClasses(outputFiles) + JvmClasses( + files = outputFiles, + mainClasses = mainClasses, + ) + } + } } + private fun findMainClasses(outputFiles: Map): Set = + outputFiles.mapNotNull { (name, bytes) -> + if (!name.endsWith(".class")) return@mapNotNull null + val reader = ClassReader(bytes) + var hasMain = false + val visitor = object : ClassVisitor(ASM9) { + override fun visitMethod( + access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array? + ): MethodVisitor? { + if (name == "main" && descriptor == "([Ljava/lang/String;)V" && (access and ACC_PUBLIC != 0) && (access and ACC_STATIC != 0)) { + hasMain = true + } + return null + } + } + reader.accept(visitor, SKIP_CODE or SKIP_DEBUG or SKIP_FRAMES) + if (hasMain) name.removeSuffix(".class") else null + }.toSet() + private fun execute( files: List, - coreEnvironment: KotlinCoreEnvironment, - block: (output: OutputDirectory, compilation: Compiled) -> ExecutionResult - ): ExecutionResult { - return try { - val (errors, analysis) = errorAnalyzer.errorsFrom( - files = files, - coreEnvironment = coreEnvironment, - projectType = ProjectType.JAVA - ) - return if (errorAnalyzer.isOnlyWarnings(errors)) { - val compilation = compile(files, analysis, coreEnvironment) - if (compilation.files.isEmpty()) - return ProgramOutput(restriction = ExecutorMessages.NO_COMPILERS_FILE_FOUND).asExecutionResult() - val output = write(compilation) - try { - block(output, compilation).also { - it.addWarnings(errors) + block: (output: OutputDirectory, compilation: JvmClasses) -> ExecutionResult + ): ExecutionResult = try { + when (val compilationResult = compile(files)) { + is Compiled -> { + usingTempDirectory { outputDir -> + val output = write(compilationResult.result, outputDir) + block(output, compilationResult.result).also { + it.addWarnings(compilationResult.compilerDiagnostics) } - } finally { - output.path.toAbsolutePath().toFile().deleteRecursively() } - } else ExecutionResult(errors) - } catch (e: Exception) { - ExecutionResult(exception = e.toExceptionDescriptor()) + } + + is NotCompiled -> ExecutionResult(compilerDiagnostics = compilationResult.compilerDiagnostics) } + } catch (e: Exception) { + ExecutionResult(exception = e.toExceptionDescriptor()) } - private fun write(compiled: Compiled): OutputDirectory { - val dir = System.getProperty("java.io.tmpdir") + private fun write(classes: JvmClasses, outputDir: Path): OutputDirectory { val libDir = librariesFile.jvm.absolutePath - val sessionId = UUID.randomUUID().toString().replace("-", "") - val outputDir = Paths.get(dir, sessionId) val policy = policyFile.readText() .replace("%%GENERATED%%", outputDir.toString().replace('\\', '/')) .replace("%%LIB_DIR%%", libDir.replace('\\', '/')) - outputDir.resolve(policyFile.name).apply { parent.toFile().mkdirs() }.toFile().writeText(policy) - return OutputDirectory(outputDir, compiled.files.map { (name, bytes) -> - outputDir.resolve(name).let { path -> + (outputDir / policyFile.name).apply { parent.toFile().mkdirs() }.toFile().writeText(policy) + return OutputDirectory(outputDir, classes.files.map { (name, bytes) -> + (outputDir / name).let { path -> path.parent.toFile().mkdirs() Files.write(path, bytes) } }) } - private fun generationStateFor( - files: List, - analysis: Analysis, - coreEnvironment: KotlinCoreEnvironment - ): GenerationState { - val codegenFactory = getCodegenFactory(coreEnvironment) - return GenerationState.Builder( - project = files.first().project, - builderFactory = ClassBuilderFactories.BINARIES, - module = analysis.analysisResult.moduleDescriptor, - bindingContext = analysis.analysisResult.bindingContext, - files = files, - configuration = coreEnvironment.configuration - ).codegenFactory(codegenFactory).build() - } - - private fun getCodegenFactory(coreEnvironment: KotlinCoreEnvironment): CodegenFactory { - return if (coreEnvironment.configuration.getBoolean(JVMConfigurationKeys.IR)) - JvmIrCodegenFactory( - coreEnvironment.configuration, - coreEnvironment.configuration.get(CLIConfigurationKeys.PHASE_CONFIG) ?: PhaseConfig(jvmPhases) - ) else - DefaultCodegenFactory - } - private fun argsFrom( mainClass: String?, outputDirectory: OutputDirectory, @@ -154,7 +156,7 @@ class KotlinCompiler( val classPaths = (kotlinEnvironment.classpath.map { it.absolutePath } + outputDirectory.path.toAbsolutePath().toString()) .joinToString(PATH_SEPARATOR) - val policy = outputDirectory.path.resolve(policyFile.name).toAbsolutePath() + val policy = (outputDirectory.path / policyFile.name).toAbsolutePath() return CommandLineArgument( classPaths = classPaths, mainClass = mainClass, @@ -163,5 +165,4 @@ class KotlinCompiler( arguments = args ).toList() } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt index 39ab9df67..b98c17ea9 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt @@ -1,39 +1,20 @@ package com.compiler.server.compiler.components import com.compiler.server.model.* +import com.fasterxml.jackson.databind.ObjectMapper import component.KotlinEnvironment -import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig -import org.jetbrains.kotlin.backend.wasm.compileToLoweredIr -import org.jetbrains.kotlin.backend.wasm.compileWasm -import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations -import org.jetbrains.kotlin.backend.wasm.wasmPhases -import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.ir.backend.js.* -import org.jetbrains.kotlin.ir.backend.js.dce.DceDumpNameCache -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode -import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl -import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC -import org.jetbrains.kotlin.js.config.JsConfig -import org.jetbrains.kotlin.js.facade.K2JSTranslator -import org.jetbrains.kotlin.js.facade.MainCallParameters -import org.jetbrains.kotlin.js.facade.TranslationResult -import org.jetbrains.kotlin.js.facade.exceptions.TranslationException -import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.cli.js.K2JsIrCompiler import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.resolve.CompilerEnvironment import org.springframework.stereotype.Service +import kotlin.io.path.div +import kotlin.io.path.readBytes +import kotlin.io.path.readText @Service class KotlinToJSTranslator( private val kotlinEnvironment: KotlinEnvironment, - private val errorAnalyzer: ErrorAnalyzer ) { companion object { - private const val JS_CODE_FLUSH = "kotlin.kotlin.io.output.flush();\n" - private const val JS_CODE_BUFFER = "\nkotlin.kotlin.io.output.buffer;\n" - private const val JS_IR_CODE_BUFFER = "moduleId.output?.buffer_1;\n" private val JS_IR_OUTPUT_REWRITE = """ @@ -47,95 +28,143 @@ class KotlinToJSTranslator( const val BEFORE_MAIN_CALL_LINE = 4 } - fun translate( + fun translateJs( files: List, arguments: List, - coreEnvironment: KotlinCoreEnvironment, - projectType: ProjectType, - translate: (ModulesStructure, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode + translate: (List, List) -> CompilationResult + ): TranslationJSResult = try { + val compilationResult = translate(files, arguments) + val jsCode = when (compilationResult) { + is Compiled -> compilationResult.result + is NotCompiled -> null + } + TranslationJSResult(jsCode = jsCode, compilerDiagnostics = compilationResult.compilerDiagnostics) + } catch (e: Exception) { + TranslationJSResult(exception = e.toExceptionDescriptor()) + } + + fun translateWasm( + files: List, + translate: (List) -> CompilationResult ): TranslationResultWithJsCode { - val (errors, analysis) = errorAnalyzer.errorsFrom(files, coreEnvironment, projectType = projectType) return try { - if (errorAnalyzer.isOnlyWarnings(errors)) { - translate((analysis as AnalysisJs).sourceModule, arguments, coreEnvironment).also { - it.addWarnings(errors) - } - } else { - TranslationJSResult(errors = errors) + val compilationResult = translate(files) + val wasmCompilationOutput = when (compilationResult) { + is Compiled -> compilationResult.result + is NotCompiled -> return TranslationJSResult(compilerDiagnostics = compilationResult.compilerDiagnostics) } + TranslationWasmResult( + jsCode = wasmCompilationOutput.jsCode, + jsInstantiated = wasmCompilationOutput.jsInstantiated, + compilerDiagnostics = compilationResult.compilerDiagnostics, + wasm = wasmCompilationOutput.wasm, + wat = wasmCompilationOutput.wat + ) } catch (e: Exception) { TranslationJSResult(exception = e.toExceptionDescriptor()) } } - fun doTranslateWithIr( - sourceModule: ModulesStructure, - arguments: List, - coreEnvironment: KotlinCoreEnvironment - ): TranslationJSResult { - val ir = compile( - sourceModule, - kotlinEnvironment.jsIrPhaseConfig, - irFactory = IrFactoryImplForJsIC(WholeWorldStageController()) - ) - val transformer = IrModuleToJsTransformer( - ir.context, - arguments - ) - - val compiledModule: CompilerResult = transformer.generateModule( - modules = ir.allModules, - modes = setOf(TranslationMode.FULL_PROD), - relativeRequirePath = false - ) - - val jsCode = getJsCodeFromModule(compiledModule) + fun doTranslateWithIr(files: List, arguments: List): CompilationResult = + usingTempDirectory { inputDir -> + val moduleName = "moduleId" + usingTempDirectory { outputDir -> + val ioFiles = files.writeToIoFiles(inputDir) + val k2JsIrCompiler = K2JsIrCompiler() + val filePaths = ioFiles.map { it.toFile().canonicalPath } + val klibPath = (outputDir / "klib").toFile().canonicalPath + val additionalCompilerArgumentsForKLib = listOf( + "-Xir-only", + "-Xir-produce-klib-dir", + "-libraries=${kotlinEnvironment.JS_LIBRARIES.joinToString(PATH_SEPARATOR)}", + "-ir-output-dir=$klibPath", + "-ir-output-name=$moduleName", + ) + k2JsIrCompiler.tryCompilation(inputDir, ioFiles, filePaths + additionalCompilerArgumentsForKLib) + .flatMap { + k2JsIrCompiler.tryCompilation(inputDir, ioFiles, listOf( + "-Xir-only", + "-Xir-produce-js", + "-Xir-dce", + "-Xinclude=$klibPath", + "-libraries=${kotlinEnvironment.JS_LIBRARIES.joinToString(PATH_SEPARATOR)}", + "-ir-output-dir=${(outputDir / "js").toFile().canonicalPath}", + "-ir-output-name=$moduleName", + )) + } + .map { (outputDir / "js" / "$moduleName.js").readText() } + .map { it.withMainArgumentsIr(arguments, moduleName) } + .map(::redirectOutput) + } + } - val listLines = jsCode + private fun redirectOutput(code: String): String { + val listLines = code .lineSequence() .toMutableList() listLines.add(listLines.size - BEFORE_MAIN_CALL_LINE, JS_IR_OUTPUT_REWRITE) listLines.add(listLines.size - 1, JS_IR_CODE_BUFFER) - - return TranslationJSResult(listLines.joinToString("\n")) + return listLines.joinToString("\n") } - fun doTranslateWithWasm( - sourceModule: ModulesStructure, - arguments: List, - coreEnvironment: KotlinCoreEnvironment - ): TranslationWasmResult { - val (allModules, backendContext) = compileToLoweredIr( - depsDescriptors = sourceModule, - phaseConfig = PhaseConfig(wasmPhases), - irFactory = IrFactoryImpl, - exportedDeclarations = setOf(FqName("main")), - propertyLazyInitialization = true, - ) - eliminateDeadDeclarations(allModules, backendContext, DceDumpNameCache()) - val res = compileWasm( - allModules = allModules, - backendContext = backendContext, - baseFileName = "moduleId", - emitNameSection = false, - allowIncompleteImplementations = true, - generateWat = true, - generateSourceMaps = false - ) - - return TranslationWasmResult(res.jsUninstantiatedWrapper, res.jsWrapper, res.wasm, res.wat) - } - - private fun getJsCodeFromModule(compiledModule: CompilerResult): String { - val jsCodeObject = compiledModule.outputs.values.single() - - val jsCodeClass = jsCodeObject.javaClass - val jsCode = jsCodeClass.getDeclaredField("rawJsCode").let { - it.isAccessible = true - it.get(jsCodeObject) as String + fun doTranslateWithWasm(files: List): CompilationResult = + usingTempDirectory { inputDir -> + val moduleName = "moduleId" + usingTempDirectory { outputDir -> + val ioFiles = files.writeToIoFiles(inputDir) + val k2JsIrCompiler = K2JsIrCompiler() + val filePaths = ioFiles.map { it.toFile().canonicalPath } + val klibPath = (outputDir / "klib").toFile().canonicalPath + val additionalCompilerArgumentsForKLib = listOf( + "-Xwasm", + "-Xir-produce-klib-dir", + "-libraries=${kotlinEnvironment.WASM_LIBRARIES.joinToString(PATH_SEPARATOR)}", + "-ir-output-dir=$klibPath", + "-ir-output-name=$moduleName", + ) + k2JsIrCompiler.tryCompilation(inputDir, ioFiles, filePaths + additionalCompilerArgumentsForKLib) + .flatMap { + k2JsIrCompiler.tryCompilation(inputDir, ioFiles, listOf( + "-Xwasm", + "-Xwasm-generate-wat", + "-Xir-produce-js", + "-Xir-dce", + "-Xinclude=$klibPath", + "-libraries=${kotlinEnvironment.WASM_LIBRARIES.joinToString(PATH_SEPARATOR)}", + "-ir-output-dir=${(outputDir / "wasm").toFile().canonicalPath}", + "-ir-output-name=$moduleName", + )) + } + .map { + WasmTranslationSuccessfulOutput( + jsCode = (outputDir / "wasm" / "$moduleName.uninstantiated.mjs").readText(), + jsInstantiated = (outputDir / "wasm" / "$moduleName.mjs").readText(), + wasm = (outputDir / "wasm" / "$moduleName.wasm").readBytes(), + wat = (outputDir / "wasm" / "$moduleName.wat").readText(), + ) + } + } } - return jsCode - } -} \ No newline at end of file +} + +private fun String.withMainArgumentsIr(arguments: List, moduleName: String): String { + val postfix = """| main([]); + | return _; + |}(typeof $moduleName === 'undefined' ? {} : $moduleName); + |""".trimMargin() + if (!endsWith(postfix)) return this + val objectMapper = ObjectMapper() + return this.removeSuffix(postfix) + """| main([${arguments.joinToString { objectMapper.writeValueAsString(it) }}]); + | return _; + |}(typeof $moduleName === 'undefined' ? {} : $moduleName); + |""".trimMargin() +} + +data class WasmTranslationSuccessfulOutput( + val jsCode: String, + val jsInstantiated: String, + val wasm: ByteArray, + val wat: String?, +) diff --git a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt index dd730cbc4..4f82ae5e2 100644 --- a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt @@ -37,7 +37,7 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe ) = kotlinProjectExecutor.complete(project, line, ch) @PostMapping("/highlight") - fun highlightEndpoint(@RequestBody project: Project): Map> = + fun highlightEndpoint(@RequestBody project: Project): CompilerDiagnostics = kotlinProjectExecutor.highlight(project) } @@ -45,4 +45,4 @@ class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExe class VersionRestController(private val kotlinProjectExecutor: KotlinProjectExecutor) { @GetMapping("/versions") fun getKotlinVersionEndpoint(): List = listOf(kotlinProjectExecutor.getVersion()) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/compiler/server/model/ErrorDescriptor.kt b/src/main/kotlin/com/compiler/server/model/ErrorDescriptor.kt index b2f4d3326..a3813c0c7 100644 --- a/src/main/kotlin/com/compiler/server/model/ErrorDescriptor.kt +++ b/src/main/kotlin/com/compiler/server/model/ErrorDescriptor.kt @@ -7,7 +7,7 @@ import model.Completion @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) data class ErrorDescriptor( - val interval: TextInterval, + val interval: TextInterval?, val message: String, val severity: ProjectSeveriry, val className: String? = null, diff --git a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt index 19c5c4f87..d8a46e6c2 100644 --- a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt +++ b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt @@ -1,9 +1,20 @@ package com.compiler.server.model import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize open class ExecutionResult( - open var errors: Map> = emptyMap(), + @field:JsonProperty("errors") + open var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics(), open var exception: ExceptionDescriptor? = null ) { var text: String = "" @@ -11,27 +22,46 @@ open class ExecutionResult( field = unEscapeOutput(value) } - fun addWarnings(warnings: Map>) { - errors = warnings + fun addWarnings(warnings: CompilerDiagnostics) { + compilerDiagnostics = warnings } fun hasErrors() = - textWithError() || exception != null || errors.any { (_, value) -> value.any { it.severity == ProjectSeveriry.ERROR } } + textWithError() || exception != null || compilerDiagnostics.any { it.severity == ProjectSeveriry.ERROR } private fun textWithError() = text.startsWith(ERROR_STREAM_START) } +class CompilerDiagnosticsSerializer : JsonSerializer() { + override fun serialize(value: CompilerDiagnostics, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeObject(value.map) + } +} + +class CompilerDiagnosticsDeserializer : JsonDeserializer() { + private val reference = object : TypeReference>>() {} + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): CompilerDiagnostics { + return p.readValueAs>>(reference).let(::CompilerDiagnostics) + } +} + +@JsonSerialize(using = CompilerDiagnosticsSerializer::class) +@JsonDeserialize(using = CompilerDiagnosticsDeserializer::class) +data class CompilerDiagnostics( + val map: Map> = mapOf() +): List by map.values.flatten() + abstract class TranslationResultWithJsCode( open val jsCode: String?, - errors: Map>, + compilerDiagnostics: CompilerDiagnostics, exception: ExceptionDescriptor? -) : ExecutionResult(errors, exception) +) : ExecutionResult(compilerDiagnostics, exception) data class TranslationJSResult( override val jsCode: String? = null, override var exception: ExceptionDescriptor? = null, - override var errors: Map> = emptyMap() -) : TranslationResultWithJsCode(jsCode, errors, exception) + override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() +) : TranslationResultWithJsCode(jsCode, compilerDiagnostics, exception) data class TranslationWasmResult( override val jsCode: String? = null, @@ -39,15 +69,15 @@ data class TranslationWasmResult( val wasm: ByteArray, val wat: String?, override var exception: ExceptionDescriptor? = null, - override var errors: Map> = emptyMap() -) : TranslationResultWithJsCode(jsCode, errors, exception) + override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() +) : TranslationResultWithJsCode(jsCode, compilerDiagnostics, exception) @JsonInclude(JsonInclude.Include.NON_EMPTY) class JunitExecutionResult( val testResults: Map> = emptyMap(), override var exception: ExceptionDescriptor? = null, - override var errors: Map> = emptyMap() -) : ExecutionResult(errors, exception) + override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() +) : ExecutionResult(compilerDiagnostics, exception) private fun unEscapeOutput(value: String) = value.replace("&lt;".toRegex(), "<") .replace("&gt;".toRegex(), ">") diff --git a/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt b/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt index 307927a6c..72a3af724 100644 --- a/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt +++ b/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt @@ -26,7 +26,7 @@ data class ProgramOutput( standardOutput.isBlank() -> ExecutionResult() else -> { try { - // coroutines can produced incorrect output. see example in `base coroutines test 7` + // coroutines can produce incorrect output. see example in `base coroutines test 7` if (standardOutput.startsWith("{")) outputMapper.readValue(standardOutput, ExecutionResult::class.java) else { val result = outputMapper.readValue("{" + standardOutput.substringAfter("{"), ExecutionResult::class.java) diff --git a/src/main/kotlin/com/compiler/server/model/TextInterval.kt b/src/main/kotlin/com/compiler/server/model/TextInterval.kt index b61171484..ecaff34f1 100644 --- a/src/main/kotlin/com/compiler/server/model/TextInterval.kt +++ b/src/main/kotlin/com/compiler/server/model/TextInterval.kt @@ -3,7 +3,10 @@ package com.compiler.server.model import com.intellij.openapi.editor.Document data class TextInterval(val start: TextPosition, val end: TextPosition) { - data class TextPosition(val line: Int, val ch: Int) + data class TextPosition(val line: Int, val ch: Int) : Comparable { + override fun compareTo(other: TextPosition): Int = compareValuesBy(this, other, { it.line }, { it.ch }) + } + companion object { fun from(start: Int, end: Int, currentDocument: Document): TextInterval { val lineNumberForElementStart = currentDocument.getLineNumber(start) @@ -23,4 +26,4 @@ data class TextInterval(val start: TextPosition, val end: TextPosition) { ) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt index b271a2ac3..d3470d7f5 100644 --- a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt +++ b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt @@ -7,7 +7,7 @@ import com.compiler.server.model.bean.VersionInfo import component.KotlinEnvironment import model.Completion import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.ir.backend.js.ModulesStructure +import org.jetbrains.kotlin.psi.KtFile import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -15,7 +15,6 @@ import org.springframework.stereotype.Component class KotlinProjectExecutor( private val kotlinCompiler: KotlinCompiler, private val completionProvider: CompletionProvider, - private val errorAnalyzer: ErrorAnalyzer, private val version: VersionInfo, private val kotlinToJSTranslator: KotlinToJSTranslator, private val kotlinEnvironment: KotlinEnvironment, @@ -27,23 +26,30 @@ class KotlinProjectExecutor( fun run(project: Project): ExecutionResult { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } - kotlinCompiler.run(files, environment, project.args) + kotlinCompiler.run(files, project.args) }.also { logExecutionResult(project, it) } } fun test(project: Project): ExecutionResult { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } - kotlinCompiler.test(files, environment) + kotlinCompiler.test(files) }.also { logExecutionResult(project, it) } } - fun convertToJsIr(project: Project): TranslationResultWithJsCode { - return convertJsWithConverter(project, ProjectType.JS_IR, kotlinToJSTranslator::doTranslateWithIr) + fun convertToJsIr(project: Project): TranslationJSResult { + return convertJsWithConverter(project, kotlinToJSTranslator::doTranslateWithIr) + } + + fun compileToJvm(project: Project): CompilationResult { + val files = kotlinEnvironment.environment { environment -> + getFilesFrom(project, environment).map { it.kotlinFile } + } + return kotlinCompiler.compile(files) } fun convertToWasm(project: Project): TranslationResultWithJsCode { - return convertJsWithConverter(project, ProjectType.WASM, kotlinToJSTranslator::doTranslateWithWasm) + return convertWasmWithConverter(project, kotlinToJSTranslator::doTranslateWithWasm) } fun complete(project: Project, line: Int, character: Int): List { @@ -57,39 +63,41 @@ class KotlinProjectExecutor( } } } + + private fun CompilerDiagnostics.withoutInfoDiagnostics() = CompilerDiagnostics( + map.mapValues { (_, l) -> l.filter { it.severity != ProjectSeveriry.INFO } }.filterValues { it.isNotEmpty() } + ) - fun highlight(project: Project): Map> { - return kotlinEnvironment.environment { environment -> - val files = getFilesFrom(project, environment).map { it.kotlinFile } - try { - errorAnalyzer.errorsFrom( - files = files, - coreEnvironment = environment, - projectType = project.confType - ).errors - } catch (e: Exception) { - log.warn("Exception in getting highlight. Project: $project", e) - emptyMap() - } + fun highlight(project: Project): CompilerDiagnostics = try { + when (project.confType) { + ProjectType.JAVA, ProjectType.JUNIT -> compileToJvm(project).compilerDiagnostics + ProjectType.CANVAS, ProjectType.JS, ProjectType.JS_IR -> convertToJsIr(project).compilerDiagnostics + ProjectType.WASM -> convertToWasm(project).compilerDiagnostics } - } + } catch (e: Exception) { + log.warn("Exception in getting highlight. Project: $project", e) + CompilerDiagnostics(emptyMap()) + }.withoutInfoDiagnostics() fun getVersion() = version private fun convertJsWithConverter( project: Project, - projectType: ProjectType, - converter: (ModulesStructure, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode + converter: (List, List) -> CompilationResult + ): TranslationJSResult { + return kotlinEnvironment.environment { environment -> + val files = getFilesFrom(project, environment).map { it.kotlinFile } + kotlinToJSTranslator.translateJs(files, project.args.split(" "), converter) + }.also { logExecutionResult(project, it) } + } + + private fun convertWasmWithConverter( + project: Project, + converter: (List) -> CompilationResult ): TranslationResultWithJsCode { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } - kotlinToJSTranslator.translate( - files, - project.args.split(" "), - environment, - projectType, - converter - ) + kotlinToJSTranslator.translateWasm(files, converter) }.also { logExecutionResult(project, it) } } @@ -104,4 +112,4 @@ class KotlinProjectExecutor( private fun getFilesFrom(project: Project, coreEnvironment: KotlinCoreEnvironment) = project.files.map { KotlinFile.from(coreEnvironment.project, it.name, it.text) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/compiler/server/AfterBackendDiagnosticsTest.kt b/src/test/kotlin/com/compiler/server/AfterBackendDiagnosticsTest.kt new file mode 100644 index 000000000..1c9a2e4e4 --- /dev/null +++ b/src/test/kotlin/com/compiler/server/AfterBackendDiagnosticsTest.kt @@ -0,0 +1,20 @@ +package com.compiler.server + +import com.compiler.server.base.BaseExecutorTest +import com.compiler.server.base.errorContains +import org.junit.jupiter.api.Test + +class AfterBackendDiagnosticsTest : BaseExecutorTest() { + @Test + fun `declaration clash test`() { + val diagnostics = highlight(code = """ + fun main() {} + class A { constructor(x: Int); constructor(x: UInt) } + """.trimIndent()) + errorContains(diagnostics, """ + Platform declaration clash: The following declarations have the same JVM signature ((I)V): + constructor A(x: Int) defined in A + constructor A(x: UInt) defined in A + """.trimIndent()) + } +} diff --git a/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt b/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt index ce8d21afc..2b1621dce 100644 --- a/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt +++ b/src/test/kotlin/com/compiler/server/CommandLineArgumentsTest.kt @@ -28,7 +28,7 @@ class CommandLineArgumentsTest : BaseExecutorTest() { runJsIr( code = "fun main(args: Array) {\n println(args[0])\n println(args[1])\n}", args = "0 1", - contains = "main(['0', '1']);" + contains = """main(["0", "1"]);""" ) } @@ -37,7 +37,7 @@ class CommandLineArgumentsTest : BaseExecutorTest() { runJsIr( code = "fun main(args: Array) {\n println(args[0])\n println(args[1])\n}", args = "alex1 alex2", - contains = "main(['alex1', 'alex2']);" + contains = """main(["alex1", "alex2"]);""" ) } } \ No newline at end of file diff --git a/src/test/kotlin/com/compiler/server/HighlightTest.kt b/src/test/kotlin/com/compiler/server/HighlightTest.kt index de1ebfb96..264cced56 100644 --- a/src/test/kotlin/com/compiler/server/HighlightTest.kt +++ b/src/test/kotlin/com/compiler/server/HighlightTest.kt @@ -11,19 +11,19 @@ class HighlightTest : BaseExecutorTest() { @Test fun `base highlight ok`() { val highlights = highlight("\nfun main() {\n println(\"Hello, world!!!\")\n}") - Assertions.assertTrue(highlights.values.flatten().isEmpty()) + Assertions.assertTrue(highlights.isEmpty()) { highlights.toString() } } @Test fun `base highlight js ok`() { val highlights = highlightJS("\nfun main() {\n println(\"Hello, world!!!\")\n}") - Assertions.assertTrue(highlights.values.flatten().isEmpty()) + Assertions.assertTrue(highlights.isEmpty()) { highlights.toString() } } @Test fun `base highlight wasm ok`() { val highlights = highlightWasm("\nfun main() {\n println(\"Hello, world!!!\")\n}") - Assertions.assertTrue(highlights.values.flatten().isEmpty()) + Assertions.assertTrue(highlights.isEmpty()) } @Test @@ -112,4 +112,4 @@ class HighlightTest : BaseExecutorTest() { errorContains(highlights, "No value passed for parameter 'that'") errorContains(highlights, "Function invocation 'to(...)' expected") } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt index 0d9da4a88..3e24d5b1d 100644 --- a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt @@ -15,9 +15,29 @@ class JvmRunnerTest : BaseExecutorTest() { @Test fun `no main class jvm test`() { - run( + runWithException( code = "fun main1() {\n println(\"sdf\")\n}", - contains = "No main method found in project" + contains = "IllegalArgumentException", + message = "No main method found in project", + ) + } + + @Test + fun `multiple main classes jvm test`() { + runWithException( + code = """ + fun main() { + println("sdf") + } + class A { + companion object { + @JvmStatic + fun main(x: Array) { + } + } + }""".trimIndent(), + contains = "IllegalArgumentException", + message = "Multiple classes in project contain main methods found: FileKt, A", ) } diff --git a/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt b/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt index 0dc947655..c84b7e935 100644 --- a/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt +++ b/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt @@ -26,7 +26,7 @@ class ResourceCompileTest : BaseExecutorTest() { val code = file.readText() val jvmResult = run(code, "") - val errorsJvm = validateErrors(jvmResult.errors) + val errorsJvm = validateErrors(jvmResult.compilerDiagnostics) if (errorsJvm != null) badMap[file.path + ":JVM"] = errorsJvm } @@ -37,7 +37,7 @@ class ResourceCompileTest : BaseExecutorTest() { val code = file.readText() val jsResult = translateToJsIr(code) - val errorsJs = validateErrors(jsResult.errors) + val errorsJs = validateErrors(jsResult.compilerDiagnostics) if (errorsJs != null) badMap[file.path + ":JS"] = errorsJs } @@ -46,8 +46,8 @@ class ResourceCompileTest : BaseExecutorTest() { } } - private fun validateErrors(errors: Map>): String? { - val errs = errors.map { it.value }.flatten().filter { it.severity == ProjectSeveriry.ERROR } + private fun validateErrors(errors: List): String? { + val errs = errors.filter { it.severity == ProjectSeveriry.ERROR } if (errs.isNotEmpty()) { return renderErrorDescriptors(errs) } diff --git a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt index 0a8fa5e35..5a796dd0c 100644 --- a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt +++ b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt @@ -56,7 +56,7 @@ class BaseExecutorTest { fun translateToJsIr(code: String) = testRunner.translateToJsIr(code) - fun runWithException(code: String, contains: String) = testRunner.runWithException(code, contains) + fun runWithException(code: String, contains: String, message: String? = null) = testRunner.runWithException(code, contains, message) fun version() = testRunner.getVersion() -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/compiler/server/base/DiagnosticUtils.kt b/src/test/kotlin/com/compiler/server/base/DiagnosticUtils.kt index d7316259d..c303df735 100644 --- a/src/test/kotlin/com/compiler/server/base/DiagnosticUtils.kt +++ b/src/test/kotlin/com/compiler/server/base/DiagnosticUtils.kt @@ -4,23 +4,15 @@ import com.compiler.server.model.ErrorDescriptor import com.compiler.server.model.ExecutionResult import com.compiler.server.model.ProjectSeveriry -val ErrorDescriptor.isError: Boolean - get() = severity == ProjectSeveriry.ERROR - val ExecutionResult.hasErrors: Boolean - get() = errors.hasErrors - -val Map>.hasErrors: Boolean - get() = asSequence() - .flatMap { it.value.asSequence() } - .any { it.isError } + get() = compilerDiagnostics.hasErrors +val List.hasErrors: Boolean + get() = any { it.severity == ProjectSeveriry.ERROR } -val ExecutionResult.filterOnlyErrors: List - get() = errors.filterOnlyErrors -val Map>.filterOnlyErrors: List - get() = flatMap { it.value }.filter { it.isError } +val List.filterOnlyErrors: List + get() = filter { it.severity == ProjectSeveriry.ERROR } fun renderErrorDescriptors(errors: List): String { @@ -29,4 +21,5 @@ fun renderErrorDescriptors(errors: List): String { } } -fun ErrorDescriptor.renderErrorDescriptor(): String = "(${interval.start.line}, ${interval.end.line}): $message" +fun ErrorDescriptor.renderErrorDescriptor(): String = + interval?.let { "(${it.start.line}, ${it.end.line}): $message" } ?: message diff --git a/src/test/kotlin/com/compiler/server/base/TestUtils.kt b/src/test/kotlin/com/compiler/server/base/TestUtils.kt index 6819afb2f..b1c64b8f7 100644 --- a/src/test/kotlin/com/compiler/server/base/TestUtils.kt +++ b/src/test/kotlin/com/compiler/server/base/TestUtils.kt @@ -1,11 +1,13 @@ package com.compiler.server.base -import com.compiler.server.model.* +import com.compiler.server.model.ErrorDescriptor +import com.compiler.server.model.ExecutionResult +import com.compiler.server.model.ProjectSeveriry import org.junit.jupiter.api.Assertions -internal fun ExecutionResult.assertNoErrors() = errors.assertNoErrors() +internal fun ExecutionResult.assertNoErrors() = compilerDiagnostics.assertNoErrors() -internal fun Map>.assertNoErrors() { +internal fun List.assertNoErrors() { Assertions.assertFalse(hasErrors) { "No errors expected, but the following errors were found:\n" + "\n" + @@ -13,21 +15,21 @@ internal fun Map>.assertNoErrors() { } } -internal fun errorContains(highlights: Map>, message: String) { - Assertions.assertTrue(highlights.values.flatten().map { it.message }.any { it.contains(message) }) { +internal fun errorContains(highlights: List, message: String) { + Assertions.assertTrue(highlights.any { it.message.contains(message) }) { "Haven't found diagnostic with message $message, actual diagnostics:\n" + "\n" + - renderErrorDescriptors(highlights.values.flatten()) + renderErrorDescriptors(highlights) } - Assertions.assertTrue(highlights.values.flatten().map { it.severity }.any { it == ProjectSeveriry.ERROR }) + Assertions.assertTrue(highlights.any { it.severity == ProjectSeveriry.ERROR }) { highlights.toString() } } -internal fun warningContains(highlights: Map>, message: String) { - Assertions.assertTrue(highlights.values.flatten().map { it.message }.any { it.contains(message) }) { +internal fun warningContains(highlights: List, message: String) { + Assertions.assertTrue(highlights.any { it.message.contains(message) }) { "Haven't found diagnostic with message $message, actual diagnostics:\n" + "\n" + - renderErrorDescriptors(highlights.values.flatten()) + renderErrorDescriptors(highlights) } - Assertions.assertTrue(highlights.values.flatten().map { it.className }.any { it == "WARNING" }) - Assertions.assertTrue(highlights.values.flatten().map { it.severity }.any { it == ProjectSeveriry.WARNING }) -} \ No newline at end of file + Assertions.assertTrue(highlights.any { it.className == "WARNING" }) { highlights.toString() } + Assertions.assertTrue(highlights.any { it.severity == ProjectSeveriry.WARNING }) { highlights.toString() } +} diff --git a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt index db4a3ed03..1f4c7a86a 100644 --- a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt +++ b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt @@ -6,7 +6,6 @@ import com.compiler.server.base.renderErrorDescriptors import com.compiler.server.model.* import com.compiler.server.service.KotlinProjectExecutor import model.Completion -import org.jetbrains.kotlin.utils.addToStdlib.assertedCast import org.junit.jupiter.api.Assertions import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -62,7 +61,7 @@ class TestProjectRunner { return kotlinProjectExecutor.convertToJsIr(project) } - fun runWithException(code: String, contains: String): ExecutionResult { + fun runWithException(code: String, contains: String, message: String? = null): ExecutionResult { val project = generateSingleProject(text = code) val result = kotlinProjectExecutor.run(project) Assertions.assertNotNull(result.exception, "Test result should no be a null") @@ -70,6 +69,7 @@ class TestProjectRunner { result.exception?.fullName?.contains(contains) == true, "Actual: ${result.exception?.message}, Expected: $contains" ) + if (message != null) Assertions.assertEquals(message, result.exception?.message) return result } @@ -110,17 +110,17 @@ class TestProjectRunner { return kotlinProjectExecutor.complete(project, line, character) } - fun highlight(code: String): Map> { + fun highlight(code: String): CompilerDiagnostics { val project = generateSingleProject(text = code) return kotlinProjectExecutor.highlight(project) } - fun highlightJS(code: String): Map> { + fun highlightJS(code: String): CompilerDiagnostics { val project = generateSingleProject(text = code, projectType = ProjectType.JS_IR) return kotlinProjectExecutor.highlight(project) } - fun highlightWasm(code: String): Map> { + fun highlightWasm(code: String): CompilerDiagnostics { val project = generateSingleProject(text = code, projectType = ProjectType.WASM) return kotlinProjectExecutor.highlight(project) } @@ -139,7 +139,7 @@ class TestProjectRunner { result.text.contains(contains), """ Actual: ${result.text} Expected: $contains - Result: ${result.errors} + Result: ${result.compilerDiagnostics} """.trimIndent() ) return result @@ -153,7 +153,7 @@ class TestProjectRunner { val result = kotlinProjectExecutor.convert(project) Assertions.assertNotNull(result, "Test result should no be a null") Assertions.assertFalse(result.hasErrors) { - "Test contains errors!\n\n" + renderErrorDescriptors(result.errors.filterOnlyErrors) + "Test contains errors!\n\n" + renderErrorDescriptors(result.compilerDiagnostics.filterOnlyErrors) } Assertions.assertTrue(result.jsCode!!.contains(contains), "Actual: ${result.jsCode}. \n Expected: $contains") } @@ -166,7 +166,7 @@ class TestProjectRunner { if (result !is TranslationWasmResult) { Assertions.assertFalse(result.hasErrors) { - "Test contains errors!\n\n" + renderErrorDescriptors(result.errors.filterOnlyErrors) + "Test contains errors!\n\n" + renderErrorDescriptors(result.compilerDiagnostics.filterOnlyErrors) } } @@ -213,4 +213,4 @@ class TestProjectRunner { process.waitFor() return inputStream.reader().readText() } -} \ No newline at end of file +} From bb5cfd25b36f127d870d5acb79e0c85570217f90 Mon Sep 17 00:00:00 2001 From: "Evgeniy.Zhelenskiy" Date: Wed, 13 Dec 2023 17:48:53 +0100 Subject: [PATCH 2/6] Support packages --- .../server/compiler/components/KotlinCompiler.kt | 2 +- src/test/kotlin/com/compiler/server/JvmRunnerTest.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt index 573231c7a..3c8fce3e8 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt @@ -111,7 +111,7 @@ class KotlinCompiler( } } reader.accept(visitor, SKIP_CODE or SKIP_DEBUG or SKIP_FRAMES) - if (hasMain) name.removeSuffix(".class") else null + if (hasMain) name.removeSuffix(".class").replace(File.separatorChar, '.') else null }.toSet() private fun execute( diff --git a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt index 3e24d5b1d..f2e16a3e9 100644 --- a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt @@ -13,6 +13,14 @@ class JvmRunnerTest : BaseExecutorTest() { ) } + @Test + fun `execute test JVM different package`() { + run( + code = "package com.example\nfun main() {\n println(\"Hello, world!!!\")\n}", + contains = "Hello, world!!!" + ) + } + @Test fun `no main class jvm test`() { runWithException( @@ -59,5 +67,4 @@ class JvmRunnerTest : BaseExecutorTest() { contains = version().substringBefore("-") ) } - } From ef5b2f451d5031846280808e0e38686afea2ea01 Mon Sep 17 00:00:00 2001 From: zoobestik Date: Wed, 13 Dec 2023 19:46:37 +0100 Subject: [PATCH 3/6] Fix degradation: incorrect `interval` and enable deprecation messages. Also made changes to `CliUtils.kt` to handle error descriptors and text intervals correctly. --- .../server/compiler/components/CliUtils.kt | 16 ++++++++++++--- .../compiler/components/KotlinCompiler.kt | 1 + .../com/compiler/server/JvmRunnerTest.kt | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt index 9df53d536..426c5195b 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt @@ -1,5 +1,6 @@ package com.compiler.server.compiler.components +import com.compiler.server.compiler.components.IndexationProvider.Companion.UNRESOLVED_REFERENCE_PREFIX import com.compiler.server.model.CompilerDiagnostics import com.compiler.server.model.ErrorDescriptor import com.compiler.server.model.ProjectSeveriry @@ -16,6 +17,7 @@ import java.nio.file.Paths import java.util.* import kotlin.io.path.* +fun minusOne(value: Int) = if (value > 0) value - 1 else value sealed class CompilationResult { abstract val compilerDiagnostics: CompilerDiagnostics @@ -64,8 +66,8 @@ fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List ProjectSeveriry.INFO } val errorFilePath = location?.path?.let(::Path)?.outputFilePathString() ?: defaultFileName - val errorDescriptor = ErrorDescriptor(textInterval, message, messageSeverity, className = "WARNING".takeIf { messageSeverity == ProjectSeveriry.WARNING }) + + val errorDescriptor = + ErrorDescriptor(textInterval, message, messageSeverity, className = messageSeverity.name.let { + when { + !message.startsWith(UNRESOLVED_REFERENCE_PREFIX) && severity == ERROR -> "red_wavy_line" + else -> it + } + }) + diagnosticsMap.getOrPut(errorFilePath) { mutableListOf() }.add(errorDescriptor) return "" } diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt index 3c8fce3e8..a969ea1c8 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt @@ -75,6 +75,7 @@ class KotlinCompiler( "-cp", kotlinEnvironment.classpath.joinToString(PATH_SEPARATOR) { it.absolutePath }, "-module-name", "web-module", "-no-stdlib", "-no-reflect", + "-progressive", "-d", outputDir.absolutePathString() ) K2JVMCompiler().tryCompilation(inputDir, ioFiles, arguments) { diff --git a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt index f2e16a3e9..733b067af 100644 --- a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt @@ -2,6 +2,7 @@ package com.compiler.server import com.compiler.server.base.BaseExecutorTest import org.junit.jupiter.api.Test +import kotlin.test.assertEquals class JvmRunnerTest : BaseExecutorTest() { @@ -67,4 +68,23 @@ class JvmRunnerTest : BaseExecutorTest() { contains = version().substringBefore("-") ) } + + @Test + fun `report deprecation warning`() { + val result = run( + contains = "97\n", /*language=kotlin */ code = """ + fun main() { + println("abc"[0].toInt()) + } + """.trimIndent() + ) + + assertEquals(1, result.compilerDiagnostics.size) + assertEquals(1, result.compilerDiagnostics[0].interval?.start?.line) + assertEquals(19, result.compilerDiagnostics[0].interval?.start?.ch) + assertEquals( + "'toInt(): Int' is deprecated. Conversion of Char to Number is deprecated. Use Char.code property instead.", + result.compilerDiagnostics[0].message + ) + } } From f50254f1f779f1696622468934b710ac8c501346 Mon Sep 17 00:00:00 2001 From: Konstantin Chernenko Date: Thu, 14 Dec 2023 11:21:33 +0100 Subject: [PATCH 4/6] Update src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt Co-authored-by: Evgeniy Zhelenskiy <55230817+zhelenskiy@users.noreply.github.com> --- .../kotlin/com/compiler/server/compiler/components/CliUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt index 426c5195b..119c87269 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt @@ -17,7 +17,7 @@ import java.nio.file.Paths import java.util.* import kotlin.io.path.* -fun minusOne(value: Int) = if (value > 0) value - 1 else value +private fun minusOne(value: Int) = if (value > 0) value - 1 else value sealed class CompilationResult { abstract val compilerDiagnostics: CompilerDiagnostics From 89e544a59e2d890f371b7c6e2fe56602032a996f Mon Sep 17 00:00:00 2001 From: Konstantin Chernenko Date: Thu, 14 Dec 2023 11:22:48 +0100 Subject: [PATCH 5/6] chore; simplize `className` statement Co-authored-by: Evgeniy Zhelenskiy <55230817+zhelenskiy@users.noreply.github.com> --- .../com/compiler/server/compiler/components/CliUtils.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt index 119c87269..69a3f8369 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt @@ -77,13 +77,8 @@ fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List "red_wavy_line" - else -> it - } - }) + val className = if (!message.startsWith(UNRESOLVED_REFERENCE_PREFIX) && severity == ERROR) "red_wavy_line" else messageSeverity.name + val errorDescriptor = ErrorDescriptor(textInterval, message, messageSeverity, className) diagnosticsMap.getOrPut(errorFilePath) { mutableListOf() }.add(errorDescriptor) return "" From d4ae69563066a4eae6d6fec25ce349bab234d47c Mon Sep 17 00:00:00 2001 From: "Evgeniy.Zhelenskiy" Date: Tue, 19 Dec 2023 18:05:03 +0100 Subject: [PATCH 6/6] Fix JS tests --- .../com/compiler/server/compiler/components/CliUtils.kt | 2 +- .../kotlin/com/compiler/server/model/ExecutionResult.kt | 3 +++ .../com/compiler/server/service/KotlinProjectExecutor.kt | 6 +----- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt index 69a3f8369..8c9a397f9 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CliUtils.kt @@ -73,7 +73,7 @@ fun CLICompiler<*>.tryCompilation(inputDirectory: Path, inputFiles: List ProjectSeveriry.ERROR STRONG_WARNING, WARNING -> ProjectSeveriry.WARNING - INFO, LOGGING, OUTPUT -> ProjectSeveriry.INFO + INFO, LOGGING, OUTPUT -> return "" } val errorFilePath = location?.path?.let(::Path)?.outputFilePathString() ?: defaultFileName diff --git a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt index d8a46e6c2..9d7691206 100644 --- a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt +++ b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt @@ -60,6 +60,7 @@ abstract class TranslationResultWithJsCode( data class TranslationJSResult( override val jsCode: String? = null, override var exception: ExceptionDescriptor? = null, + @field:JsonProperty("errors") override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() ) : TranslationResultWithJsCode(jsCode, compilerDiagnostics, exception) @@ -69,6 +70,7 @@ data class TranslationWasmResult( val wasm: ByteArray, val wat: String?, override var exception: ExceptionDescriptor? = null, + @field:JsonProperty("errors") override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() ) : TranslationResultWithJsCode(jsCode, compilerDiagnostics, exception) @@ -76,6 +78,7 @@ data class TranslationWasmResult( class JunitExecutionResult( val testResults: Map> = emptyMap(), override var exception: ExceptionDescriptor? = null, + @field:JsonProperty("errors") override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() ) : ExecutionResult(compilerDiagnostics, exception) diff --git a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt index d3470d7f5..ced520536 100644 --- a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt +++ b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt @@ -63,10 +63,6 @@ class KotlinProjectExecutor( } } } - - private fun CompilerDiagnostics.withoutInfoDiagnostics() = CompilerDiagnostics( - map.mapValues { (_, l) -> l.filter { it.severity != ProjectSeveriry.INFO } }.filterValues { it.isNotEmpty() } - ) fun highlight(project: Project): CompilerDiagnostics = try { when (project.confType) { @@ -77,7 +73,7 @@ class KotlinProjectExecutor( } catch (e: Exception) { log.warn("Exception in getting highlight. Project: $project", e) CompilerDiagnostics(emptyMap()) - }.withoutInfoDiagnostics() + } fun getVersion() = version