From 667d2b41b3ad55364205b931b30674f3a1e4efcf Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Thu, 24 Aug 2023 12:49:01 +0200 Subject: [PATCH] Use Wasm frontend for wasm target to use specific wasm checkers and libraries --- Dockerfile | 2 + build.gradle.kts | 3 + gradle.properties | 1 + indexation/build.gradle.kts | 4 +- indexation/src/main/kotlin/Main.kt | 15 +- ...tionBuilder.kt => WebIndexationBuilder.kt} | 16 +- .../server/compiler/KotlinPlatform.kt | 7 + .../compiler/components/CompletionProvider.kt | 27 ++-- .../compiler/components/ErrorAnalyzer.kt | 142 +++++++++++++----- .../compiler/components/IndexationProvider.kt | 24 ++- .../compiler/components/KotlinCompiler.kt | 2 +- .../components/KotlinToJSTranslator.kt | 68 +-------- .../KotlinPlaygroundRestController.kt | 4 +- .../com/compiler/server/model/Analysis.kt | 17 ++- .../com/compiler/server/model/Project.kt | 6 +- .../server/service/KotlinProjectExecutor.kt | 20 +-- .../com/compiler/server/HighlightTest.kt | 35 +++++ .../compiler/server/ResourceCompileTest.kt | 2 +- .../compiler/server/base/BaseExecutorTest.kt | 19 +-- .../server/generator/TestProjectRunner.kt | 21 ++- 20 files changed, 275 insertions(+), 160 deletions(-) rename indexation/src/main/kotlin/{JsIndexationBuilder.kt => WebIndexationBuilder.kt} (74%) create mode 100644 src/main/kotlin/com/compiler/server/compiler/KotlinPlatform.kt diff --git a/Dockerfile b/Dockerfile index de9dc364f..890397ad0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,8 @@ COPY --from=build /kotlin-compiler-server/${KOTLIN_LIB_JS} /kotlin-compiler-serv COPY --from=build /kotlin-compiler-server/${KOTLIN_LIB_WASM} /kotlin-compiler-server/${KOTLIN_LIB_WASM} COPY --from=build /kotlin-compiler-server/executor.policy /kotlin-compiler-server/ COPY --from=build /kotlin-compiler-server/indexes.json /kotlin-compiler-server/ +COPY --from=build /kotlin-compiler-server/indexesJs.json /kotlin-compiler-server/ +COPY --from=build /kotlin-compiler-server/indexesWasm.json /kotlin-compiler-server/ ENV PORT=8080 diff --git a/build.gradle.kts b/build.gradle.kts index aaf129806..9341dfe8f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,7 @@ val kotlinIdeVersionSuffix: String by System.getProperties() val policy: String by System.getProperties() val indexes: String by System.getProperties() val indexesJs: String by System.getProperties() +val indexesWasm: String by System.getProperties() group = "com.compiler.server" version = "$kotlinVersion-SNAPSHOT" @@ -160,6 +161,7 @@ fun generateProperties(prefix: String = "") = """ policy.file=${prefix + policy} indexes.file=${prefix + indexes} indexesJs.file=${prefix + indexesJs} + indexesWasm.file=${prefix + indexesWasm} libraries.folder.jvm=${prefix + libJVMFolder} libraries.folder.js=${prefix + libJSFolder} libraries.folder.wasm=${prefix + libWasmFolder} @@ -204,6 +206,7 @@ val buildLambda by tasks.creating(Zip::class) { from(policy) from(indexes) from(indexesJs) + from(indexesWasm) from(libJSFolder) { into(libJSFolder) } from(libWasmFolder) { into(libWasmFolder) } from(libJVMFolder) { into(libJVMFolder) } diff --git a/gradle.properties b/gradle.properties index 3f89edfaa..74da6b3d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,3 +4,4 @@ systemProp.kotlinIdeVersionSuffix=IJ8109.175 systemProp.policy=executor.policy systemProp.indexes=indexes.json systemProp.indexesJs=indexesJs.json +systemProp.indexesWasm=indexesWasm.json diff --git a/indexation/build.gradle.kts b/indexation/build.gradle.kts index 3d9926651..fb7ccbf64 100644 --- a/indexation/build.gradle.kts +++ b/indexation/build.gradle.kts @@ -2,6 +2,7 @@ val kotlinVersion: String by System.getProperties() val kotlinIdeVersion: String by System.getProperties() val indexes: String by System.getProperties() val indexesJs: String by System.getProperties() +val indexesWasm: String by System.getProperties() plugins { kotlin("jvm") @@ -24,6 +25,7 @@ tasks.withType { args = listOf( "$rootName${File.separator}$kotlinVersion", "$rootName${File.separator}$indexes", - "$rootName${File.separator}$indexesJs" + "$rootName${File.separator}$indexesJs", + "$rootName${File.separator}$indexesWasm" ) } diff --git a/indexation/src/main/kotlin/Main.kt b/indexation/src/main/kotlin/Main.kt index 15106c44f..5d72d9aba 100644 --- a/indexation/src/main/kotlin/Main.kt +++ b/indexation/src/main/kotlin/Main.kt @@ -6,8 +6,19 @@ package indexation * Third argument is path to output file for js indexes */ fun main(args: Array) { - val (directory, outputPathJvm, outputPathJs) = args + val (directory, outputPathJvm, outputPathJs, outputPathWasm) = args val kotlinEnvironment = KotlinEnvironmentConfiguration(directory).kotlinEnvironment JvmIndexationBuilder(kotlinEnvironment = kotlinEnvironment).writeIndexesToFile(outputPathJvm) - JsIndexationBuilder(kotlinEnvironment = kotlinEnvironment).writeIndexesToFile(outputPathJs) + + WebIndexationBuilder( + kotlinEnvironment = kotlinEnvironment, + configuration = kotlinEnvironment.jsConfiguration, + libraries = kotlinEnvironment.JS_LIBRARIES + ).writeIndexesToFile(outputPathJs) + + WebIndexationBuilder( + kotlinEnvironment = kotlinEnvironment, + configuration = kotlinEnvironment.wasmConfiguration, + libraries = kotlinEnvironment.WASM_LIBRARIES + ).writeIndexesToFile(outputPathWasm) } diff --git a/indexation/src/main/kotlin/JsIndexationBuilder.kt b/indexation/src/main/kotlin/WebIndexationBuilder.kt similarity index 74% rename from indexation/src/main/kotlin/JsIndexationBuilder.kt rename to indexation/src/main/kotlin/WebIndexationBuilder.kt index b54f8ab06..4c1005f1c 100644 --- a/indexation/src/main/kotlin/JsIndexationBuilder.kt +++ b/indexation/src/main/kotlin/WebIndexationBuilder.kt @@ -3,11 +3,15 @@ package indexation import model.ImportInfo import component.KotlinEnvironment import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport +import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule -import org.jetbrains.kotlin.js.config.JsConfig -import org.jetbrains.kotlin.resolve.CompilerEnvironment -class JsIndexationBuilder(private val kotlinEnvironment: KotlinEnvironment): IndexationBuilder() { +class WebIndexationBuilder( + private val kotlinEnvironment: KotlinEnvironment, + private val configuration: CompilerConfiguration, + private val libraries: List +): IndexationBuilder() { + override fun getAllIndexes(): List = kotlinEnvironment.environment { coreEnvironment -> val project = coreEnvironment.project @@ -15,8 +19,8 @@ class JsIndexationBuilder(private val kotlinEnvironment: KotlinEnvironment): Ind val sourceModule = prepareAnalyzedSourceModule( project, coreEnvironment.getSourceFiles(), - kotlinEnvironment.jsConfiguration, - kotlinEnvironment.JS_LIBRARIES, + configuration, + libraries, friendDependencies = emptyList(), analyzer = AnalyzerWithCompilerReport(kotlinEnvironment.jsConfiguration), ) @@ -29,4 +33,4 @@ class JsIndexationBuilder(private val kotlinEnvironment: KotlinEnvironment): Ind moduleDescriptor.allImportsInfo() }.distinct() } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/compiler/KotlinPlatform.kt b/src/main/kotlin/com/compiler/server/compiler/KotlinPlatform.kt new file mode 100644 index 000000000..84daae196 --- /dev/null +++ b/src/main/kotlin/com/compiler/server/compiler/KotlinPlatform.kt @@ -0,0 +1,7 @@ +package com.compiler.server.compiler + +enum class KotlinPlatform { + JS, + JVM, + WASM +} \ No newline at end of file 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 85cdbe94a..121f0530d 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/CompletionProvider.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/CompletionProvider.kt @@ -4,6 +4,7 @@ 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.ProjectType import com.intellij.psi.PsiElement import com.intellij.psi.tree.TokenSet import model.Completion @@ -59,16 +60,16 @@ class CompletionProvider( file: KotlinFile, line: Int, character: Int, - isJs: Boolean, + projectType: ProjectType, coreEnvironment: KotlinCoreEnvironment ): List = with(file.insert("$COMPLETION_SUFFIX ", line, character)) { elementAt(line, character)?.let { element -> - val descriptorInfo = descriptorsFrom(this, element, isJs, coreEnvironment) + val descriptorInfo = descriptorsFrom(this, element, projectType, coreEnvironment) val prefix = (if (descriptorInfo.isTipsManagerCompletion) element.text else element.parent.text) .substringBefore(COMPLETION_SUFFIX).let { if (it.endsWith(".")) "" else it } - val importCompletionVariants = if (indexationProvider.hasIndexes(isJs)) { - val (errors, _) = errorAnalyzer.errorsFrom(listOf(file.kotlinFile), coreEnvironment, isJs) - importVariants(file, prefix, errors, line, character, isJs) + val importCompletionVariants = if (indexationProvider.hasIndexes(projectType)) { + val (errors, _) = errorAnalyzer.errorsFrom(listOf(file.kotlinFile), coreEnvironment, projectType) + importVariants(file, prefix, errors, line, character, projectType) } else emptyList() descriptorInfo.descriptors.toMutableList().apply { sortWith(Comparator { a, b -> @@ -115,9 +116,9 @@ class CompletionProvider( errors: Map>, line: Int, character: Int, - isJs: Boolean + projectType: ProjectType ): List { - val importCompletionVariants = indexationProvider.getClassesByName(prefix, isJs) + val importCompletionVariants = indexationProvider.getClassesByName(prefix, projectType) ?.map { it.toCompletion() } ?: emptyList() val currentErrors = errors[file.kotlinFile.name]?.filter { it.interval.start.line == line && @@ -195,14 +196,16 @@ class CompletionProvider( private fun descriptorsFrom( file: KotlinFile, element: PsiElement, - isJs: Boolean, + projectType: ProjectType, coreEnvironment: KotlinCoreEnvironment ): DescriptorInfo { val files = listOf(file.kotlinFile) - val analysis = if (isJs.not()) - errorAnalyzer.analysisOf(files, coreEnvironment) - else - errorAnalyzer.analyzeFileForJs(files, coreEnvironment) + val analysis = when { + projectType.isJvmRelated() -> errorAnalyzer.analysisOf(files, coreEnvironment) + projectType.isJsRelated() -> errorAnalyzer.analyzeFileForJs(files, coreEnvironment) + projectType == ProjectType.WASM -> errorAnalyzer.analyzeFileForWasm(files, coreEnvironment) + else -> throw IllegalArgumentException("Unknown project type $projectType") + } return with(analysis) { (referenceVariantsFrom(element, coreEnvironment) ?: referenceVariantsFrom(element.parent, coreEnvironment))?.let { descriptors -> 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 62238ecde..72dba4af1 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/ErrorAnalyzer.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/ErrorAnalyzer.kt @@ -1,9 +1,6 @@ package com.compiler.server.compiler.components -import com.compiler.server.model.Analysis -import com.compiler.server.model.ErrorDescriptor -import com.compiler.server.model.ProjectSeveriry -import com.compiler.server.model.TextInterval +import com.compiler.server.model.* import com.intellij.openapi.util.Pair import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor @@ -12,6 +9,7 @@ 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.jvm.compiler.CliBindingTrace @@ -33,11 +31,13 @@ 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.ir.backend.js.prepareAnalyzedSourceModule +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 +import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.js.JsPlatforms +import org.jetbrains.kotlin.platform.wasm.WasmPlatforms import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension @@ -46,6 +46,7 @@ import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory +import org.jetbrains.kotlin.wasm.resolve.WasmPlatformAnalyzerServices import org.springframework.stereotype.Component @Component @@ -56,14 +57,19 @@ class ErrorAnalyzer( fun errorsFrom( files: List, coreEnvironment: KotlinCoreEnvironment, - isJs: Boolean + projectType: ProjectType ): ErrorsAndAnalysis { - val analysis = if (isJs.not()) analysisOf(files, coreEnvironment) else analyzeFileForJs(files, coreEnvironment) + val analysis = when { + projectType.isJvmRelated() ->analysisOf(files, coreEnvironment) + projectType.isJsRelated() -> analyzeFileForJs(files, coreEnvironment) + projectType == ProjectType.WASM -> analyzeFileForWasm(files, coreEnvironment) + else -> throw IllegalArgumentException("Unknown platform: $projectType") + } return ErrorsAndAnalysis( errorsFrom( analysis.analysisResult.bindingContext.diagnostics.all(), - files.associate { it.name to anylizeErrorsFrom(it, isJs) }, - isJs + files.associate { it.name to anylizeErrorsFrom(it, projectType) }, + projectType ), analysis ) @@ -99,7 +105,7 @@ class ErrorAnalyzer( files = files ) != null } - return Analysis( + return AnalysisJvm( componentProvider = componentProvider, analysisResult = AnalysisResult.success(trace.bindingContext, moduleDescriptor) ) @@ -128,13 +134,19 @@ class ErrorAnalyzer( sourceModule.getModuleDescriptor(it) as ModuleDescriptorImpl } - val analysisResult = TopDownAnalyzerFacadeForJSIR.analyzeFiles( + val builtInModuleDescriptor = sourceModule.builtInModuleDescriptor + + val analyzer = AnalyzerWithCompilerReport(kotlinEnvironment.jsConfiguration) + val analyzerFacade = TopDownAnalyzerFacadeForJSIR + val analysisResult = analyzerFacade.analyzeFiles( mainModule.files, project, kotlinEnvironment.jsConfiguration, mds, emptyList(), - AnalyzerWithCompilerReport(kotlinEnvironment.jsConfiguration).targetEnvironment + analyzer.targetEnvironment, + thisIsBuiltInsModule = builtInModuleDescriptor == null, + customBuiltInsModule = builtInModuleDescriptor ) val context = ContextForNewModule( @@ -146,8 +158,74 @@ class ErrorAnalyzer( context.module.setDependencies(dependencies.toList()) val trace = CliBindingTrace() val providerFactory = FileBasedDeclarationProviderFactory(context.storageManager, files) - val analyzerAndProvider = createContainerForTopDownAnalyzerForJs(context, trace, providerFactory) - return Analysis( + val analyzerAndProvider = createContainerForTopDownAnalyzerForJs(context, trace, providerFactory, JsPlatforms.defaultJsPlatform, JsPlatformAnalyzerServices) + + val hasErrors = analyzer.hasErrors() + + sourceModule.jsFrontEndResult = ModulesStructure.JsFrontEndResult(analysisResult, hasErrors) + + return AnalysisJs( + sourceModule = sourceModule, + componentProvider = analyzerAndProvider.second, + analysisResult = analysisResult + ) + } + + fun analyzeFileForWasm(files: List, coreEnvironment: KotlinCoreEnvironment): Analysis { + val project = coreEnvironment.project + val configuration = JsConfig( + project, + kotlinEnvironment.wasmConfiguration, + CompilerEnvironment, + emptyList(), + kotlinEnvironment.WASM_LIBRARIES.toSet() + ) + + val mainModule = MainModule.SourceFiles(files) + val sourceModule = ModulesStructure( + project, + mainModule, + kotlinEnvironment.wasmConfiguration, + kotlinEnvironment.WASM_LIBRARIES, + emptyList() + ) + + val mds = sourceModule.allDependencies.map { + sourceModule.getModuleDescriptor(it) as ModuleDescriptorImpl + } + + val builtInModuleDescriptor = sourceModule.builtInModuleDescriptor + + val analyzer = AnalyzerWithCompilerReport(kotlinEnvironment.jsConfiguration) + val analyzerFacade = TopDownAnalyzerFacadeForWasmJs + val analysisResult = analyzerFacade.analyzeFiles( + mainModule.files, + project, + kotlinEnvironment.wasmConfiguration, + mds, + emptyList(), + analyzer.targetEnvironment, + thisIsBuiltInsModule = builtInModuleDescriptor == null, + customBuiltInsModule = builtInModuleDescriptor + ) + + val context = ContextForNewModule( + projectContext = ProjectContext(project, "COMPILER-SERVER-JS"), + moduleName = Name.special("<" + configuration.moduleId + ">"), + builtIns = WasmPlatformAnalyzerServices.builtIns, platform = null + ) + val dependencies = mutableSetOf(context.module) + mds + WasmPlatformAnalyzerServices.builtIns.builtInsModule + context.module.setDependencies(dependencies.toList()) + val trace = CliBindingTrace() + val providerFactory = FileBasedDeclarationProviderFactory(context.storageManager, files) + val analyzerAndProvider = createContainerForTopDownAnalyzerForJs(context, trace, providerFactory, WasmPlatforms.Default, WasmPlatformAnalyzerServices) + + val hasErrors = analyzer.hasErrors() + + sourceModule.jsFrontEndResult = ModulesStructure.JsFrontEndResult(analysisResult, hasErrors) + + return AnalysisJs( + sourceModule = sourceModule, componentProvider = analyzerAndProvider.second, analysisResult = analysisResult ) @@ -156,9 +234,9 @@ class ErrorAnalyzer( fun errorsFrom( diagnostics: Collection, errors: Map>, - isJs: Boolean + projectType: ProjectType ): Map> { - return (errors and errorsFrom(diagnostics, isJs)).map { (fileName, errors) -> + 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) { @@ -172,7 +250,7 @@ class ErrorAnalyzer( fun isOnlyWarnings(errors: Map>) = errors.none { it.value.any { error -> error.severity == ProjectSeveriry.ERROR } } - private fun anylizeErrorsFrom(file: PsiFile, isJs: Boolean): List { + private fun anylizeErrorsFrom(file: PsiFile, projectType: ProjectType): List { class Visitor : PsiElementVisitor() { val errors = mutableListOf() override fun visitElement(element: PsiElement) { @@ -193,32 +271,26 @@ class ErrorAnalyzer( message = it.errorDescription, severity = ProjectSeveriry.ERROR, className = "red_wavy_line", - imports = completionsForErrorMessage(it.errorDescription, isJs) + imports = completionsForErrorMessage(it.errorDescription, projectType) ) } } - private fun computeDependencies(module: ModuleDescriptorImpl, config: JsConfig): List { - val allDependencies = ArrayList() - allDependencies.add(module) - config.moduleDescriptors.mapTo(allDependencies) { it } - allDependencies.add(JsPlatformAnalyzerServices.builtIns.builtInsModule) - return allDependencies - } - private fun createContainerForTopDownAnalyzerForJs( moduleContext: ModuleContext, bindingTrace: BindingTrace, - declarationProviderFactory: DeclarationProviderFactory + declarationProviderFactory: DeclarationProviderFactory, + platform: TargetPlatform, + analyzerServices: PlatformDependentAnalyzerServices ): Pair { val container = composeContainer( "TopDownAnalyzerForJs", - JsPlatformAnalyzerServices.platformConfigurator.platformSpecificContainer + analyzerServices.platformConfigurator.platformSpecificContainer ) { configureModule( moduleContext = moduleContext, - platform = JsPlatforms.defaultJsPlatform, - analyzerServices = JsPlatformAnalyzerServices, + platform = platform, + analyzerServices = analyzerServices, trace = bindingTrace, languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT, optimizingOptions = null, @@ -241,7 +313,7 @@ class ErrorAnalyzer( private fun errorsFrom( diagnostics: Collection, - isJs: Boolean + projectType: ProjectType ) = diagnostics.mapNotNull { diagnostic -> diagnostic.psiFile.virtualFile?.let { val render = DefaultErrorMessages.render(diagnostic) @@ -251,7 +323,7 @@ class ErrorAnalyzer( if (textRanges.hasNext()) { var className = diagnostic.severity.name val imports = if (diagnostic.factory === Errors.UNRESOLVED_REFERENCE) { - completionsForErrorMessage(render, isJs) + completionsForErrorMessage(render, projectType) } else null if (!(diagnostic.factory === Errors.UNRESOLVED_REFERENCE) && diagnostic.severity == Severity.ERROR) { className = "red_wavy_line" @@ -281,12 +353,12 @@ class ErrorAnalyzer( .map { it.key to it.value.fold(emptyList()) { acc, (_, errors) -> acc + errors } } .toMap() - private fun completionsForErrorMessage(message: String, isJs: Boolean): List? { - if (!indexationProvider.hasIndexes(isJs) || + private fun completionsForErrorMessage(message: String, projectType: ProjectType): List? { + if (!indexationProvider.hasIndexes(projectType) || !message.startsWith(IndexationProvider.UNRESOLVED_REFERENCE_PREFIX) ) return null val name = message.removePrefix(IndexationProvider.UNRESOLVED_REFERENCE_PREFIX) - return indexationProvider.getClassesByName(name, isJs)?.map { suggest -> suggest.toCompletion() } + return indexationProvider.getClassesByName(name, projectType)?.map { suggest -> suggest.toCompletion() } } } diff --git a/src/main/kotlin/com/compiler/server/compiler/components/IndexationProvider.kt b/src/main/kotlin/com/compiler/server/compiler/components/IndexationProvider.kt index 0d5121f0f..3888de89e 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/IndexationProvider.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/IndexationProvider.kt @@ -1,5 +1,6 @@ package com.compiler.server.compiler.components +import com.compiler.server.model.ProjectType import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import model.ImportInfo @@ -11,7 +12,8 @@ import java.io.File @Component class IndexationProvider( @Value("\${indexes.file}") private val indexesFileName: String, - @Value("\${indexesJs.file}") private val indexesJsFileName: String + @Value("\${indexesJs.file}") private val indexesJsFileName: String, + @Value("\${indexesWasm.file}") private val indexesWasmFileName: String ) { companion object { const val UNRESOLVED_REFERENCE_PREFIX = "Unresolved reference: " @@ -26,10 +28,24 @@ class IndexationProvider( initIndexes(indexesJsFileName) } - fun hasIndexes(isJs: Boolean) = if (isJs) jsIndexes != null else jvmIndexes != null + private val wasmIndexes: Map>? by lazy { + initIndexes(indexesWasmFileName) + } + + fun hasIndexes(projectType: ProjectType) = when { + projectType.isJsRelated() -> jsIndexes != null + projectType.isJvmRelated() -> jvmIndexes != null + projectType == ProjectType.WASM -> wasmIndexes != null + else -> throw IllegalArgumentException("Platform $projectType not found") + } - fun getClassesByName(name: String, isJs: Boolean): List? { - val indexes = if (isJs) jsIndexes else jvmIndexes + fun getClassesByName(name: String, projectType: ProjectType): List? { + val indexes = when { + projectType.isJsRelated() -> jsIndexes + projectType.isJvmRelated() -> jvmIndexes + projectType == ProjectType.WASM -> wasmIndexes + else -> throw IllegalArgumentException("Platform $projectType not found") + } return indexes?.get(name) } 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 82359de1c..3b529d9b1 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt @@ -84,7 +84,7 @@ class KotlinCompiler( val (errors, analysis) = errorAnalyzer.errorsFrom( files = files, coreEnvironment = coreEnvironment, - isJs = false + projectType = ProjectType.JAVA ) return if (errorAnalyzer.isOnlyWarnings(errors)) { val compilation = compile(files, analysis, coreEnvironment) 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 07d7dcf1a..39ab9df67 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinToJSTranslator.kt @@ -51,12 +51,13 @@ class KotlinToJSTranslator( files: List, arguments: List, coreEnvironment: KotlinCoreEnvironment, - translate: (List, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode + projectType: ProjectType, + translate: (ModulesStructure, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode ): TranslationResultWithJsCode { - val (errors, _) = errorAnalyzer.errorsFrom(files, coreEnvironment, isJs = true) + val (errors, analysis) = errorAnalyzer.errorsFrom(files, coreEnvironment, projectType = projectType) return try { if (errorAnalyzer.isOnlyWarnings(errors)) { - translate(files, arguments, coreEnvironment).also { + translate((analysis as AnalysisJs).sourceModule, arguments, coreEnvironment).also { it.addWarnings(errors) } } else { @@ -67,57 +68,11 @@ class KotlinToJSTranslator( } } - @Throws(TranslationException::class) - fun doTranslate( - files: List, - arguments: List, - coreEnvironment: KotlinCoreEnvironment - ): TranslationJSResult { - val currentProject = coreEnvironment.project - val configuration = JsConfig( - currentProject, - kotlinEnvironment.jsConfiguration, - CompilerEnvironment, - kotlinEnvironment.JS_METADATA_CACHE, - kotlinEnvironment.JS_LIBRARIES.toSet() - ) - val reporter = object : JsConfig.Reporter() { - override fun error(message: String) {} - override fun warning(message: String) {} - } - val translator = K2JSTranslator(configuration) - val result = translator.translate( - reporter = reporter, - files = files, - mainCallParameters = MainCallParameters.mainWithArguments(arguments) - ) - return if (result is TranslationResult.Success) { - TranslationJSResult(JS_CODE_FLUSH + result.getCode() + JS_CODE_BUFFER) - } else { - val errors = HashMap>() - for (psiFile in files) { - errors[psiFile.name] = ArrayList() - } - errorAnalyzer.errorsFrom(result.diagnostics.all(), errors, isJs = true) - TranslationJSResult(errors = errors) - } - } - fun doTranslateWithIr( - files: List, + sourceModule: ModulesStructure, arguments: List, coreEnvironment: KotlinCoreEnvironment ): TranslationJSResult { - val currentProject = coreEnvironment.project - - val sourceModule = prepareAnalyzedSourceModule( - currentProject, - files, - kotlinEnvironment.jsConfiguration, - kotlinEnvironment.JS_LIBRARIES, - friendDependencies = emptyList(), - analyzer = AnalyzerWithCompilerReport(kotlinEnvironment.jsConfiguration), - ) val ir = compile( sourceModule, kotlinEnvironment.jsIrPhaseConfig, @@ -147,21 +102,10 @@ class KotlinToJSTranslator( } fun doTranslateWithWasm( - files: List, + sourceModule: ModulesStructure, arguments: List, coreEnvironment: KotlinCoreEnvironment ): TranslationWasmResult { - val currentProject = coreEnvironment.project - - val sourceModule = prepareAnalyzedSourceModule( - currentProject, - files, - kotlinEnvironment.wasmConfiguration, - kotlinEnvironment.WASM_LIBRARIES, - friendDependencies = emptyList(), - analyzer = AnalyzerWithCompilerReport(kotlinEnvironment.wasmConfiguration), - ) - val (allModules, backendContext) = compileToLoweredIr( depsDescriptors = sourceModule, phaseConfig = PhaseConfig(wasmPhases), diff --git a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt index 5dffbdf1d..89366c45b 100644 --- a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt @@ -40,8 +40,8 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr "run" -> { when (project.confType) { ProjectType.JAVA -> kotlinProjectExecutor.run(project) - ProjectType.JS, ProjectType.CANVAS -> throw LegacyJsException() - ProjectType.JS_IR -> kotlinProjectExecutor.convertToJsIr(project) + ProjectType.JS -> throw LegacyJsException() + ProjectType.JS_IR, ProjectType.CANVAS -> kotlinProjectExecutor.convertToJsIr(project) ProjectType.WASM -> kotlinProjectExecutor.convertToWasm(project) ProjectType.JUNIT -> kotlinProjectExecutor.test(project) } diff --git a/src/main/kotlin/com/compiler/server/model/Analysis.kt b/src/main/kotlin/com/compiler/server/model/Analysis.kt index 08dd73167..51a0a8f10 100644 --- a/src/main/kotlin/com/compiler/server/model/Analysis.kt +++ b/src/main/kotlin/com/compiler/server/model/Analysis.kt @@ -2,5 +2,20 @@ package com.compiler.server.model import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.container.ComponentProvider +import org.jetbrains.kotlin.ir.backend.js.ModulesStructure -data class Analysis(val componentProvider: ComponentProvider, val analysisResult: AnalysisResult) \ No newline at end of file +interface Analysis { + val componentProvider: ComponentProvider + val analysisResult: AnalysisResult +} + +data class AnalysisJvm( + override val componentProvider: ComponentProvider, + override val analysisResult: AnalysisResult +) : Analysis + +data class AnalysisJs( + val sourceModule: ModulesStructure, + override val componentProvider: ComponentProvider, + override val analysisResult: AnalysisResult +) : Analysis \ No newline at end of file diff --git a/src/main/kotlin/com/compiler/server/model/Project.kt b/src/main/kotlin/com/compiler/server/model/Project.kt index 27a12cc28..b2a5bf86c 100644 --- a/src/main/kotlin/com/compiler/server/model/Project.kt +++ b/src/main/kotlin/com/compiler/server/model/Project.kt @@ -16,10 +16,12 @@ data class ProjectFile(val text: String = "", val name: String = "") enum class ProjectType(@JsonValue val id: String) { JAVA("java"), JUNIT("junit"), - CANVAS("canvas"), JS("js"), + CANVAS("canvas"), JS_IR("js-ir"), WASM("wasm"); - fun isJsRelated(): Boolean = this == JS || this == JS_IR || this == CANVAS + fun isJvmRelated(): Boolean = this == JAVA || this == JUNIT + + fun isJsRelated(): Boolean = this == JS_IR || this == JS || this == CANVAS } \ 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 c06d6280a..b271a2ac3 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.psi.KtFile +import org.jetbrains.kotlin.ir.backend.js.ModulesStructure import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -38,24 +38,19 @@ class KotlinProjectExecutor( }.also { logExecutionResult(project, it) } } - fun convertToJs(project: Project): TranslationResultWithJsCode { - return convertJsWithConverter(project, kotlinToJSTranslator::doTranslate) - } - fun convertToJsIr(project: Project): TranslationResultWithJsCode { - return convertJsWithConverter(project, kotlinToJSTranslator::doTranslateWithIr) + return convertJsWithConverter(project, ProjectType.JS_IR, kotlinToJSTranslator::doTranslateWithIr) } fun convertToWasm(project: Project): TranslationResultWithJsCode { - return convertJsWithConverter(project, kotlinToJSTranslator::doTranslateWithWasm) + return convertJsWithConverter(project, ProjectType.WASM, kotlinToJSTranslator::doTranslateWithWasm) } fun complete(project: Project, line: Int, character: Int): List { return kotlinEnvironment.environment { val file = getFilesFrom(project, it).first() try { - val isJs = project.confType.isJsRelated() - completionProvider.complete(file, line, character, isJs, it) + completionProvider.complete(file, line, character, project.confType, it) } catch (e: Exception) { log.warn("Exception in getting completions. Project: $project", e) emptyList() @@ -67,11 +62,10 @@ class KotlinProjectExecutor( return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } try { - val isJs = project.confType.isJsRelated() errorAnalyzer.errorsFrom( files = files, coreEnvironment = environment, - isJs = isJs + projectType = project.confType ).errors } catch (e: Exception) { log.warn("Exception in getting highlight. Project: $project", e) @@ -84,7 +78,8 @@ class KotlinProjectExecutor( private fun convertJsWithConverter( project: Project, - converter: (List, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode + projectType: ProjectType, + converter: (ModulesStructure, List, KotlinCoreEnvironment) -> TranslationResultWithJsCode ): TranslationResultWithJsCode { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } @@ -92,6 +87,7 @@ class KotlinProjectExecutor( files, project.args.split(" "), environment, + projectType, converter ) }.also { logExecutionResult(project, it) } diff --git a/src/test/kotlin/com/compiler/server/HighlightTest.kt b/src/test/kotlin/com/compiler/server/HighlightTest.kt index df888792d..de1ebfb96 100644 --- a/src/test/kotlin/com/compiler/server/HighlightTest.kt +++ b/src/test/kotlin/com/compiler/server/HighlightTest.kt @@ -20,6 +20,12 @@ class HighlightTest : BaseExecutorTest() { Assertions.assertTrue(highlights.values.flatten().isEmpty()) } + @Test + fun `base highlight wasm ok`() { + val highlights = highlightWasm("\nfun main() {\n println(\"Hello, world!!!\")\n}") + Assertions.assertTrue(highlights.values.flatten().isEmpty()) + } + @Test fun `base highlight unused variable`() { val highlights = highlight("fun main() {\n\tval a = \"d\"\n}") @@ -32,6 +38,12 @@ class HighlightTest : BaseExecutorTest() { warningContains(highlights, "Variable 'a' is never used") } + @Test + fun `base highlight unused variable wasm`() { + val highlights = highlightWasm("fun main() {\n\tval a = \"d\"\n}") + warningContains(highlights, "Variable 'a' is never used") + } + @Test fun `base highlight false condition`() { val highlights = highlight("fun main() {\n val a: String = \"\"\n if (a == null) print(\"b\")\n}") @@ -44,6 +56,12 @@ class HighlightTest : BaseExecutorTest() { warningContains(highlights, "Condition 'a == null' is always 'false'") } + @Test + fun `base highlight false condition wasm`() { + val highlights = highlightWasm("fun main() {\n val a: String = \"\"\n if (a == null) print(\"b\")\n}") + warningContains(highlights, "Condition 'a == null' is always 'false'") + } + @Test fun `highlight Unresolved reference`() { val highlights = highlight("fun main() {\n dfsdf\n}") @@ -56,6 +74,12 @@ class HighlightTest : BaseExecutorTest() { errorContains(highlights, "Unresolved reference: dfsdf") } + @Test + fun `highlight wasm Unresolved reference`() { + val highlights = highlightWasm("fun main() {\n dfsdf\n}") + errorContains(highlights, "Unresolved reference: dfsdf") + } + @Test fun `highlight Type inference failed`() { val highlights = highlight("fun main() {\n \"sdf\".to\n}") @@ -77,4 +101,15 @@ class HighlightTest : BaseExecutorTest() { errorContains(highlights, "No value passed for parameter 'that'") errorContains(highlights, "Function invocation 'to(...)' expected") } + + @Test + fun `highlight wasm Type inference failed`() { + val highlights = highlightWasm("fun main() {\n \"sdf\".to\n}") + errorContains( + highlights, + "Not enough information to infer type variable B" + ) + 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/ResourceCompileTest.kt b/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt index cf6b6ea5a..0dc947655 100644 --- a/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt +++ b/src/test/kotlin/com/compiler/server/ResourceCompileTest.kt @@ -36,7 +36,7 @@ class ResourceCompileTest : BaseExecutorTest() { testFilesJS.forEach { file -> val code = file.readText() - val jsResult = translateToJs(code) + val jsResult = translateToJsIr(code) val errorsJs = validateErrors(jsResult.errors) if (errorsJs != null) badMap[file.path + ":JS"] = errorsJs } diff --git a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt index eb4cccfef..0a8fa5e35 100644 --- a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt +++ b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt @@ -28,25 +28,12 @@ class BaseExecutorTest { fun highlightJS(code: String) = testRunner.highlightJS(code) + fun highlightWasm(code: String) = testRunner.highlightWasm(code) + fun run(code: String, contains: String, args: String = "") = testRunner.run(code, contains, args) fun run(code: List, contains: String) = testRunner.multiRun(code, contains) - fun runJs( - code: String, - contains: String, - args: String = "" - ) = testRunner.runJs(code, contains, args) { project -> - convertToJs(project) - } - - fun runJs( - code: List, - contains: String - ) = testRunner.multiRunJs(code, contains) { project -> - convertToJs(project) - } - fun runJsIr( code: String, contains: String, @@ -67,7 +54,7 @@ class BaseExecutorTest { contains: String ) = testRunner.runWasm(code, contains) - fun translateToJs(code: String) = testRunner.translateToJs(code) + fun translateToJsIr(code: String) = testRunner.translateToJsIr(code) fun runWithException(code: String, contains: String) = testRunner.runWithException(code, contains) diff --git a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt index deb10dbd4..db4a3ed03 100644 --- a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt +++ b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt @@ -6,6 +6,7 @@ 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 @@ -56,9 +57,9 @@ class TestProjectRunner { convertWasmAndTest(project, contains) } - fun translateToJs(code: String): TranslationResultWithJsCode { + fun translateToJsIr(code: String): TranslationResultWithJsCode { val project = generateSingleProject(text = code, projectType = ProjectType.JS_IR) - return kotlinProjectExecutor.convertToJs(project) + return kotlinProjectExecutor.convertToJsIr(project) } fun runWithException(code: String, contains: String): ExecutionResult { @@ -119,6 +120,11 @@ class TestProjectRunner { return kotlinProjectExecutor.highlight(project) } + fun highlightWasm(code: String): Map> { + val project = generateSingleProject(text = code, projectType = ProjectType.WASM) + return kotlinProjectExecutor.highlight(project) + } + fun getVersion() = kotlinProjectExecutor.getVersion().version private fun executeTest(vararg test: String): JunitExecutionResult? { @@ -156,7 +162,16 @@ class TestProjectRunner { project: Project, contains: String, ): ExecutionResult { - val result = kotlinProjectExecutor.convertToWasm(project) as TranslationWasmResult + val result = kotlinProjectExecutor.convertToWasm(project) + + if (result !is TranslationWasmResult) { + Assertions.assertFalse(result.hasErrors) { + "Test contains errors!\n\n" + renderErrorDescriptors(result.errors.filterOnlyErrors) + } + } + + result as TranslationWasmResult + Assertions.assertNotNull(result, "Test result should no be a null") val tmpDir = createTempDirectory()