diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index 2b6336cb1a3..4ba6299f4a9 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -123,7 +123,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() { ) @Argument( value = "-module-kind", - valueDescription = "{plain|amd|commonjs|umd}", + valueDescription = "{plain|amd|commonjs|umd|es}", description = "Kind of the JS module generated by the compiler" ) var moduleKind: String? by NullableStringFreezableVar(K2JsArgumentConstants.MODULE_PLAIN) diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index eb3af666cd2..f26bd255bdd 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -551,7 +551,7 @@ class K2JsIrCompiler : CLICompiler() { var moduleKind: ModuleKind? = if (moduleKindName != null) moduleKindMap[moduleKindName] else ModuleKind.PLAIN if (moduleKind == null) { messageCollector.report( - ERROR, "Unknown module kind: $moduleKindName. Valid values are: plain, amd, commonjs, umd", null + ERROR, "Unknown module kind: $moduleKindName. Valid values are: plain, amd, commonjs, umd, es", null ) moduleKind = ModuleKind.PLAIN } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt deleted file mode 100644 index 6b87ac533e0..00000000000 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/IrToJs.kt +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.ir.backend.js.codegen - -import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext -import org.jetbrains.kotlin.ir.backend.js.LoweredIr -import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity.* -import org.jetbrains.kotlin.ir.backend.js.export.* -import org.jetbrains.kotlin.ir.backend.js.lower.JsCodeOutliningLowering -import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrFileToJsTransformer -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.processClassModels -import org.jetbrains.kotlin.ir.backend.js.utils.* -import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.types.classOrNull -import org.jetbrains.kotlin.ir.util.file -import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable -import org.jetbrains.kotlin.ir.util.hasInterfaceParent -import org.jetbrains.kotlin.ir.util.isInterface -import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid -import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlin.ir.visitors.acceptVoid -import org.jetbrains.kotlin.js.backend.ast.* -import org.jetbrains.kotlin.serialization.js.ModuleKind -import kotlin.math.abs - -interface CompilerOutputSink { - fun write(module: String, path: String, content: String) -} - -class JsGenerationOptions( - val jsExtension: String = "js", - val generatePackageJson: Boolean = false, - val generateTypeScriptDefinitions: Boolean = false, -) - -class IrToJs( - private val backendContext: JsIrBackendContext, - private val guid: (IrDeclaration) -> String, - private val outputSink: CompilerOutputSink, - private val mainArguments: List?, - private val granularity: JsGenerationGranularity, - private val mainModuleName: String, - private val options: JsGenerationOptions, -) { - val indexFileName = "index.${options.jsExtension}" - - val FileUnit.initFunctionName - get() = "KotlinInit$" + sanitizeName(pathToJsModule(file)) - - sealed class CodegenUnitReference - object ThisUnitReference : CodegenUnitReference() - inner class OtherUnitReference( - module: IrModuleFragment, - ) : CodegenUnitReference() { - // Path to entry point of other module from "top-level", e.g. directory which contains all other modules - val importPath = "./" + module.jsModuleName + "/" + indexFileName - } - - abstract class CodegenUnit { - abstract val packageFragments: Iterable - abstract val externalPackageFragments: Iterable - abstract fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference - abstract val pathToKotlinModulesRoot: String - } - - inner class FileUnit(val file: IrFile, val externalFile: IrFile?) : CodegenUnit() { - override val packageFragments = - listOf(file) - - override val externalPackageFragments = - listOfNotNull(externalFile) - - override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference = - when (val declarationFile = declaration.file) { - file -> ThisUnitReference - else -> OtherUnitReference(declarationFile.module) - } - - override val pathToKotlinModulesRoot: String by lazy { - "../".repeat(file.fqName.pathSegments().size + 1) - } - } - - inner class ModuleUnit(val module: IrModuleFragment) : CodegenUnit() { - override val packageFragments: Iterable = - module.files - - override val externalPackageFragments: Iterable = - packageFragments.mapNotNull { backendContext.externalPackageFragment[it.symbol] } - - override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference = - when (val declarationModule = declaration.file.module) { - module -> ThisUnitReference - else -> OtherUnitReference(declarationModule) - } - - override val pathToKotlinModulesRoot: String = "../" - } - - class WholeProgramUnit( - val modules: Iterable, - val externalModules: Iterable - ) : CodegenUnit() { - override val packageFragments: Iterable = - modules.flatMap { it.files } - - override val externalPackageFragments: Iterable - get() = externalModules - - override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference = - ThisUnitReference - - override val pathToKotlinModulesRoot: String - get() = "../" - } - - private fun pathToJsModule(file: IrFile): String = - "${fileJsRootModuleName(file)}/${fileJsSubModulePath(file)}" - - private fun fileJsRootModuleName(file: IrFile): String = - when (granularity) { - WHOLE_PROGRAM -> mainModuleName - PER_MODULE, PER_FILE -> file.module.jsModuleName - } - - private fun fileJsSubModulePath(file: IrFile): String = - when (granularity) { - WHOLE_PROGRAM, PER_MODULE -> indexFileName - - PER_FILE -> { - val maybeSingleOpenClass = (file.declarations.singleOrNull() as? IrClass)?.takeIf { - it.modality == Modality.ABSTRACT || it.modality == Modality.OPEN - } - - val hash = abs((maybeSingleOpenClass?.let { guid(it) } ?: file.path).hashCode()) - val filePrefix = maybeSingleOpenClass?.name?.asString()?.let { sanitizeName(it) + ".class" } ?: file.name - val fileName = "${filePrefix}_$hash.${options.jsExtension}" - val packagePath = file.fqName.pathSegments().joinToString("") { it.identifier + "/" } - "$packagePath$fileName" - } - } - - class GeneratedUnit( - val jsStatements: List, - val exportedDeclarations: List, - ) - - fun generateUnit(unit: CodegenUnit): GeneratedUnit { - val exportedDeclarations: List = - with(ExportModelGenerator(backendContext, generateNamespacesForPackages = false)) { - (unit.externalPackageFragments + unit.packageFragments).flatMap { packageFragment -> - generateExport(packageFragment) - } - } - - val stableNames: Set = collectStableNames(unit) - val nameGenerator = NewNamerImpl(backendContext, unit, guid, stableNames) - - val staticContext = JsStaticContext( - backendContext = backendContext, - irNamer = nameGenerator, - globalNameScope = nameGenerator.staticNames - ) - - val declarationStatements: List = unit.packageFragments.flatMap { - StaticMembersLowering(backendContext).lower(it as IrFile) - it.accept(IrFileToJsTransformer(), staticContext).statements - } - - val preDeclarationBlock = JsCompositeBlock() - val postDeclarationBlock = JsCompositeBlock() - processClassModels(staticContext.classModels, preDeclarationBlock, postDeclarationBlock) - - val statements = mutableListOf() - statements += nameGenerator.internalImports.values - statements += preDeclarationBlock - statements += declarationStatements - statements += postDeclarationBlock - - // Generate module initialization - - val initializerBlock = staticContext.initializerBlock - when (unit) { - is WholeProgramUnit, is ModuleUnit -> { - // Run initialization during ES module initialization - statements += initializerBlock - } - - is FileUnit -> { - // Postpone initialization by putting it into a separate function - // Will be called later in proper order after class model is initialized - val initFunction = JsFunction(emptyScope, JsBlock(initializerBlock.statements), "init fun") - initFunction.name = JsName(unit.initFunctionName, false) - statements += initFunction.makeStmt() - statements += JsExport(initFunction.name) - } - } - - // Generate internal export - - val internalExports = mutableListOf() - fun export(declaration: IrDeclarationWithName) { - internalExports += JsExport.Element(nameGenerator.getNameForStaticDeclaration(declaration), JsName(guid(declaration), false)) - } - - for (fragment in unit.packageFragments) { - for (declaration in fragment.declarations) { - if (declaration is IrDeclarationWithName) { - if (declaration.origin == JsCodeOutliningLowering.OUTLINED_JS_CODE_ORIGIN) continue - export(declaration) - } - - // Default implementations of interface methods are nested under interface declarations in IR at this point, - // but they are effectively used as a static declaration and can be directly referenced by other codegen unit, - // thus requiring internal export - declaration.acceptChildrenVoid(object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitSimpleFunction(declaration: IrSimpleFunction) { - if (declaration.hasInterfaceParent() && declaration.body != null) { - export(declaration) - } - super.visitSimpleFunction(declaration) - } - }) - } - } - statements += JsExport(JsExport.Subject.Elements(internalExports), null) - - // Generate external export - - val globalNames = NameTable(nameGenerator.staticNames) - val exporter = ExportModelToJsStatements( - staticContext, - declareNewNamespace = { globalNames.declareFreshName(it, it) } - ) - exportedDeclarations.forEach { - statements += exporter.generateDeclarationExport( - it, - null, - esModules = true - ) - } - - return GeneratedUnit(statements, exportedDeclarations) - } - - private fun collectStableNames(unit: CodegenUnit): Set { - val newStableStaticNamesCollectorVisitor = - NewStableStaticNamesCollectorVisitor(needToCollectReferences = granularity != WHOLE_PROGRAM) - unit.packageFragments.forEach { it.acceptVoid(newStableStaticNamesCollectorVisitor) } - unit.externalPackageFragments.forEach { it.acceptVoid(newStableStaticNamesCollectorVisitor) } - - return newStableStaticNamesCollectorVisitor.collectedStableNames - } - - // Returns import statement and call expression - private fun invokeFunctionFromEntryJsFile( - function: IrFunction, - args: List = emptyList() - ): Pair { - val name = guid(function) - val importPath = if (granularity == WHOLE_PROGRAM) "./$indexFileName" else "../" + pathToJsModule(function.file) - return Pair( - JsImport(importPath, mutableListOf(JsImport.Element(name, null))), - JsInvocation(JsNameRef(name), args) - ) - } - - private fun invokeFunctionFromEntryJsFileAsStatements( - function: IrFunction, - args: List = emptyList() - ): List = - invokeFunctionFromEntryJsFile(function, args) - .let { listOf(it.first, it.second.makeStmt()) } - - fun generateModules( - mainModule: IrModuleFragment, - allModules: List - ) { - when (granularity) { - WHOLE_PROGRAM -> - generateModule(mainModule, allModules) - - PER_MODULE, - PER_FILE -> - allModules.forEach { module -> - generateModule(mainModule = module, allModules = emptyList()) - } - } - } - - fun generateModuleLevelCode(module: IrModuleFragment, statements: MutableList) { - if (mainArguments != null) { - val mainFunction = JsMainFunctionDetector(backendContext).getMainFunctionOrNull(module) - if (mainFunction != null) { - val generateArgv = mainFunction.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false - val generateContinuation = mainFunction.isLoweredSuspendFunction(backendContext) - - val mainArgumentsArray = - if (generateArgv) - JsArrayLiteral(mainArguments.map { JsStringLiteral(it) }) - else - null - - val continuation = - if (generateContinuation) { - val (import, invoke) = invokeFunctionFromEntryJsFile(backendContext.coroutineEmptyContinuation.owner.getter!!) - statements += import - invoke - } else - null - - statements += invokeFunctionFromEntryJsFileAsStatements( - mainFunction, listOfNotNull(mainArgumentsArray, continuation) - ) - } - } - - // TODO: tests -// backendContext.testRoots[module]?.let { testContainer -> -// statements += invokeFunctionFromEntryJsFileAsStatements(testContainer) -// } - } - - fun generateModule( - mainModule: IrModuleFragment, - allModules: List, - ) { - val moduleName = mainModule.jsModuleName - val indexJsStatements = mutableListOf() - val exportedDeclarations = mutableListOf() - - when (granularity) { - PER_FILE -> { - for (file in mainModule.files.sortedBy(::fileInitOrder)) { - if (file.declarations.isEmpty()) continue - - val pathToSubModule = fileJsSubModulePath(file) - indexJsStatements += JsExport(JsExport.Subject.All, fromModule = "./$pathToSubModule") - - val unit = FileUnit(file, backendContext.externalPackageFragment[file.symbol]) - val generatedUnit = generateUnit(unit) - - val importElements = JsImport.Element(unit.initFunctionName, null) - indexJsStatements += JsImport("./$pathToSubModule", mutableListOf(importElements)) - indexJsStatements += JsInvocation(JsNameRef(JsName(unit.initFunctionName, false))).makeStmt() - - exportedDeclarations += generatedUnit.exportedDeclarations - - outputSink.write( - file.module.jsModuleName, - pathToSubModule, - "// Kotlin file: ${file.path}\n" + generatedUnit.jsStatements.toJsCodeString() - ) - } - generateModuleLevelCode(mainModule, indexJsStatements) - } - - PER_MODULE -> { - val generatedUnit = generateUnit(ModuleUnit(mainModule)) - indexJsStatements += generatedUnit.jsStatements - generateModuleLevelCode(mainModule, indexJsStatements) - exportedDeclarations += generatedUnit.exportedDeclarations - } - - WHOLE_PROGRAM -> { - val generatedUnit = generateUnit(WholeProgramUnit(allModules, backendContext.externalPackageFragment.values)) - indexJsStatements += generatedUnit.jsStatements - allModules.forEach { - generateModuleLevelCode(it, indexJsStatements) - } - exportedDeclarations += generatedUnit.exportedDeclarations - } - } - - outputSink.write(moduleName, indexFileName, indexJsStatements.toJsCodeString()) - - if (options.generatePackageJson) { - outputSink.write(moduleName, "package.json", """{ "main": "$indexFileName", "type": "module" }""") - } - - if (options.generateTypeScriptDefinitions && exportedDeclarations.isNotEmpty()) { - val dts = ExportedModule(moduleName, moduleKind = ModuleKind.ES, exportedDeclarations).toTypeScript() - outputSink.write(moduleName, "index.d.ts", dts) - } - } - - private fun fileInitOrder(file: IrFile): Int = - when (val singleDeclaration = file.declarations.singleOrNull()) { - // Initialize parent classes before child classes - // TODO: Comment about open classes in separate files - is IrClass -> singleDeclaration.getInheritanceChainLength() - // Initialize regular files after all open classes - else -> Int.MAX_VALUE - } - - private fun IrClass.getInheritanceChainLength(): Int { - if (symbol == backendContext.irBuiltIns.anyClass) - return 0 - - // FIXME: Filter out interfaces - superTypes.forEach { superType -> - val superClass: IrClass? = superType.classOrNull?.owner - if (superClass != null && /* !!! */ !superClass.isInterface) - return superClass.getInheritanceChainLength() + 1 - } - - - return 1 - } -} - -private val IrModuleFragment.jsModuleName: String - get() = name.asString() - .replace("[.:@]".toRegex(), "_") - .dropWhile { it == '<' } - .dropLastWhile { it == '>' } - -private fun List.toJsCodeString(): String = - JsCompositeBlock(this).toString() - -enum class JsGenerationGranularity { - WHOLE_PROGRAM, - PER_MODULE, - PER_FILE -} - -fun generateEsModules( - ir: LoweredIr, - outputSink: CompilerOutputSink, - mainArguments: List?, - granularity: JsGenerationGranularity, - options: JsGenerationOptions, -) { - // Declaration numeration to create temporary GUID - // TODO: Replace with an actual GUID - val numerator = StaticDeclarationNumerator() - ir.allModules.forEach { numerator.add(it) } - - fun guid(declaration: IrDeclaration): String { - val name = sanitizeName((declaration as IrDeclarationWithName).name.toString()) - val number = numerator.numeration[declaration] - ?: error("Can't find number for declaration ${declaration.fqNameWhenAvailable}") - // TODO: Use shorter names in release mode - return "${name}_GUID_${number}" - } - - val ir2js = IrToJs(ir.context, ::guid, outputSink, mainArguments, granularity, ir.mainModule.jsModuleName, options) - ir2js.generateModules(ir.mainModule, ir.allModules) -} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/JsGenerationGranularity.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/JsGenerationGranularity.kt new file mode 100644 index 00000000000..15badb9851e --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/JsGenerationGranularity.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.codegen + +enum class JsGenerationGranularity { + WHOLE_PROGRAM, + PER_MODULE, + PER_FILE +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index 7a976176c3e..8809f0a3abe 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -48,7 +48,7 @@ data class ExportedConstructSignature( val returnType: ExportedType, ) : ExportedDeclaration() -class ExportedProperty( +data class ExportedProperty( val name: String, val type: ExportedType, val mutable: Boolean = true, @@ -94,7 +94,7 @@ data class ExportedObject( override val members: List, override val nestedClasses: List, override val ir: IrClass, - val irGetter: IrFunction + val irGetter: IrSimpleFunction ) : ExportedClass() class ExportedParameter( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt index 61bdd779b6a..27ad199ccb2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -5,8 +5,16 @@ package org.jetbrains.kotlin.ir.backend.js.export -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.* import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.prototypeOf +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsElementAccess +import org.jetbrains.kotlin.ir.backend.js.utils.Namer +import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope +import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.util.companionObject import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd @@ -17,8 +25,14 @@ class ExportModelToJsStatements( ) { private val namespaceToRefMap = mutableMapOf() - fun generateModuleExport(module: ExportedModule, internalModuleName: JsName): List { - return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName), esModules = false) } + fun generateModuleExport( + module: ExportedModule, + internalModuleName: JsName?, + esModules: Boolean + ): List { + return module.declarations.flatMap { + generateDeclarationExport(it, internalModuleName?.makeRef(), esModules) + } } fun generateDeclarationExport( @@ -60,17 +74,12 @@ class ExportModelToJsStatements( is ExportedFunction -> { val name = namer.getNameForStaticDeclaration(declaration.ir) - if (esModules) { - listOf(JsExport(name, alias = JsName(declaration.name, false))) - } else { - if (namespace != null) { - listOf( - jsAssignment( - jsElementAccess(declaration.name, namespace), - JsNameRef(name) - ).makeStmt() - ) - } else emptyList() + when { + namespace != null -> + listOf(jsAssignment(jsElementAccess(declaration.name, namespace), JsNameRef(name)).makeStmt()) + + esModules -> listOf(JsExport(name, alias = JsName(declaration.name, false))) + else -> emptyList() } } @@ -78,40 +87,77 @@ class ExportModelToJsStatements( is ExportedConstructSignature -> emptyList() is ExportedProperty -> { - require(namespace != null) { "Only namespaced properties are allowed" } - val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) } - val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) } - listOf(defineProperty(namespace, declaration.name, getter, setter, namer).makeStmt()) + require(namespace != null || esModules) { "Only namespaced properties are allowed" } + val getter = declaration.irGetter?.let { namer.getNameForStaticDeclaration(it) } + val setter = declaration.irSetter?.let { namer.getNameForStaticDeclaration(it) } + if (namespace == null) { + val property = JsVars.JsVar( + JsName(declaration.name, false), + JsObjectLiteral(false).apply { + getter?.let { + val fieldName = when (declaration.irGetter.origin) { + JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION -> "getInstance" + else -> "get" + } + propertyInitializers += JsPropertyInitializer(JsStringLiteral(fieldName), it.makeRef()) + } + setter?.let { propertyInitializers += JsPropertyInitializer(JsStringLiteral("set"), it.makeRef()) } + } + ) + listOf( + JsVars(property), + JsExport(property.name, JsName(declaration.name, false)) + ) + } else { + listOf(defineProperty(namespace, declaration.name, getter?.makeRef(), setter?.makeRef(), namer).makeStmt()) + } } is ErrorDeclaration -> emptyList() is ExportedObject -> { - require(namespace != null) { "Only namespaced properties are allowed" } - val newNameSpace = jsElementAccess(declaration.name, namespace) - val getter = JsNameRef(namer.getNameForStaticDeclaration(declaration.irGetter)) + require(namespace != null || esModules) { "Only namespaced properties are allowed" } + val newNameSpace = when { + namespace != null -> jsElementAccess(declaration.name, namespace) + else -> + jsElementAccess(Namer.PROTOTYPE_NAME, namer.getNameForClass(declaration.ir).makeRef()) + } val staticsExport = declaration.nestedClasses.flatMap { generateDeclarationExport(it, newNameSpace, esModules) } - listOf(defineProperty(namespace, declaration.name, getter, null, namer).makeStmt()) + staticsExport + + val objectExport = when (namespace) { + null -> generateDeclarationExport( + ExportedProperty(declaration.name, ExportedType.Primitive.Any, irGetter = declaration.irGetter), + namespace, + esModules + ) + + else -> listOf( + defineProperty( + namespace, + declaration.name, + namer.getNameForStaticDeclaration(declaration.irGetter).makeRef(), + null, + namer + ).makeStmt() + ) + } + + objectExport + staticsExport } is ExportedRegularClass -> { if (declaration.isInterface) return emptyList() - val newNameSpace = if (namespace != null) - jsElementAccess(declaration.name, namespace) - else - prototypeOf(namer.getNameForClass(declaration.ir).makeRef(), namer) val name = namer.getNameForStaticDeclaration(declaration.ir) - val klassExport = - if (esModules) { - JsExport(name, alias = JsName(declaration.name, false)) - } else { - if (namespace != null) { - jsAssignment( - newNameSpace, - JsNameRef(name) - ).makeStmt() - } else null - } + val newNameSpace = when { + namespace != null -> jsElementAccess(declaration.name, namespace) + esModules -> name.makeRef() + else -> prototypeOf(namer.getNameForClass(declaration.ir).makeRef(), namer) + } + val klassExport = when { + namespace != null -> jsAssignment(newNameSpace, JsNameRef(name)).makeStmt() + esModules -> JsExport(name, alias = JsName(declaration.name, false)) + else -> null + } // These are only used when exporting secondary constructors annotated with @JsName val staticFunctions = declaration.members diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index dd78d6b3800..74501aa36ce 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.export import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName @@ -15,11 +16,14 @@ import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.js.common.isValidES5Identifier import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -import javax.lang.model.type.IntersectionType +import org.jetbrains.kotlin.utils.addToStdlib.runIf private const val Nullable = "Nullable" private const val objects = "_objects_" +private const val declare = "declare " +private const val declareExorted = "export $declare" + +private const val NonExistent = "__NonExistent" private const val syntheticObjectNameSeparator = '$' fun ExportedModule.toTypeScript(): String { @@ -61,32 +65,36 @@ class ExportModelToTsDeclarations { return joinToString("\n") { it.toTypeScript( indent = moduleKind.indent, - prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export " + prefix = if (moduleKind == ModuleKind.PLAIN) "" else declareExorted, + esModules = moduleKind == ModuleKind.ES ) - } + generateObjectsNamespaceIfNeeded(moduleKind.indent) + } + generateObjectsNamespaceIfNeeded( + indent = moduleKind.indent, + prefix = if (moduleKind == ModuleKind.PLAIN) "" else declare, + ) } - private fun generateObjectsNamespaceIfNeeded(indent: String): String { + private fun generateObjectsNamespaceIfNeeded(indent: String, prefix: String): String { return if (objectsSyntheticProperties.isEmpty()) { "" } else { - "\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, "") + "\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, prefix) } } private fun List.toTypeScript(indent: String): String = joinToString("") { it.toTypeScript(indent) + "\n" } - private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String = + private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = "", esModules: Boolean = false): String = indent + when (this) { is ErrorDeclaration -> generateTypeScriptString() - is ExportedNamespace -> generateTypeScriptString(indent, prefix) - is ExportedFunction -> generateTypeScriptString(indent, prefix) is ExportedConstructor -> generateTypeScriptString(indent) is ExportedConstructSignature -> generateTypeScriptString(indent) - is ExportedProperty -> generateTypeScriptString(indent, prefix) - is ExportedObject -> generateTypeScriptString(indent, prefix) + is ExportedNamespace -> generateTypeScriptString(indent, prefix) + is ExportedFunction -> generateTypeScriptString(indent, prefix) is ExportedRegularClass -> generateTypeScriptString(indent, prefix) + is ExportedProperty -> generateTypeScriptString(indent, prefix, esModules) + is ExportedObject -> generateTypeScriptString(indent, prefix, esModules) } private fun ErrorDeclaration.generateTypeScriptString(): String { @@ -107,34 +115,45 @@ class ExportModelToTsDeclarations { return "new($renderedParameters): ${returnType.toTypeScript(indent)};" } - private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String): String { - val visibility = if (isProtected) "protected " else "" - val keyword = when { - isMember -> (if (isAbstract) "abstract " else "") - else -> if (mutable) "let " else "const " - } - val possibleStatic = if (isMember && isStatic) "static " else "" + private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean = false): String { + val extraIndent = "$indent " + val optional = if (isOptional) "?" else "" val containsUnresolvedChar = !name.isValidES5Identifier() - val memberName = when { - isMember && containsUnresolvedChar -> "\"$name\"" - else -> name - } - val typeToTypeScript = type.toTypeScript(indent) + val memberName = if (containsUnresolvedChar) "\"$name\"" else name + val isObjectGetter = irGetter?.origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION - return if (isMember && !isField) { - val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;" - if (!mutable) { - getter + val typeToTypeScript = type.toTypeScript(if (!isMember && esModules && isObjectGetter) extraIndent else indent) + + return if (isMember) { + val static = if (isStatic) "static " else "" + val abstract = if (isAbstract) "abstract " else "" + val visibility = if (isProtected) "protected " else "" + + if (isField) { + val readonly = if (!mutable) "readonly " else "" + "$prefix$visibility$static$abstract$readonly$memberName$optional: $typeToTypeScript;" } else { - getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);" + val getter = "$prefix$visibility$static${abstract}get $memberName(): $typeToTypeScript;" + val setter = runIf(mutable) { "\n$indent$prefix$visibility$static${abstract}set $memberName(value: $typeToTypeScript);" } + getter + setter.orEmpty() } } else { - if (!isMember && containsUnresolvedChar) { - "" - } else { - val readonly = if (isMember && !mutable) "readonly " else "" - val optional = if (isOptional) "?" else "" - "$prefix$visibility$possibleStatic$keyword$readonly$memberName$optional: $typeToTypeScript;" + when { + containsUnresolvedChar -> "" + esModules -> { + if (isObjectGetter) { + "${prefix}const $name: {\n${extraIndent}getInstance(): $typeToTypeScript;\n};" + } else { + val getter = "get(): $typeToTypeScript;" + val setter = runIf(mutable) { " set(value: $typeToTypeScript): void;" } + "${prefix}const $name: { $getter${setter.orEmpty()} };" + } + } + + else -> { + val keyword = if (mutable) "let " else "const " + "$prefix$keyword$memberName$optional: $typeToTypeScript;" + } } } } @@ -171,14 +190,19 @@ class ExportModelToTsDeclarations { return if (!isMember && containsUnresolvedChar) { "" } else { - "${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;" + "$prefix$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;" } } - private fun ExportedObject.generateTypeScriptString(indent: String, prefix: String): String { + private fun ExportedObject.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean = false): String { val shouldRenderSeparatedAbstractClass = !couldBeProperty() - var t: ExportedType = ExportedType.InlineInterfaceType(members) + val extraMembers = nestedClasses + .takeIf { !shouldRenderSeparatedAbstractClass } + ?.map { it as ExportedObject } + .orEmpty() + + var t: ExportedType = ExportedType.InlineInterfaceType(members + extraMembers) for (superInterface in superClasses + superInterfaces) { t = ExportedType.IntersectionType(t, superInterface) @@ -208,13 +232,14 @@ class ExportModelToTsDeclarations { ) return if (!shouldRenderSeparatedAbstractClass) { - property.generateTypeScriptString(indent, prefix) + property.generateTypeScriptString(indent, prefix, esModules) } else { + val className = NonExistent.takeIf { esModules }.orEmpty() + name val propertyRef = "$objects.$propertyName" val shouldCreateExtraProperty = members.isNotEmpty() || superInterfaces.isNotEmpty() || superClasses.isNotEmpty() val newSuperClass = ExportedType.ClassType(propertyRef, emptyList(), ir).takeIf { shouldCreateExtraProperty } - ExportedRegularClass( - name = name, + val classForRender = ExportedRegularClass( + name = className, isInterface = false, isAbstract = true, superClasses = listOfNotNull(newSuperClass), @@ -224,8 +249,14 @@ class ExportModelToTsDeclarations { nestedClasses = nestedClasses, ir = ir ) - .generateTypeScriptString(indent, prefix) .also { if (shouldCreateExtraProperty) objectsSyntheticProperties.add(property) } + + if (esModules && !property.isMember) { + property.copy(type = ExportedType.TypeOf(className), name = name) + .generateTypeScriptString(indent, prefix, esModules) + "\n${classForRender.generateTypeScriptString(indent, declare)}" + } else { + classForRender.generateTypeScriptString(indent, prefix) + } } } @@ -272,10 +303,7 @@ class ExportModelToTsDeclarations { val klassExport = "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}" val staticsExport = - if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript( - indent, - prefix - ) else "" + if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else "" return if (name.isValidES5Identifier()) klassExport + staticsExport else "" } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt index 393070eff9f..9af4bacfd01 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt @@ -42,7 +42,7 @@ class JsExecutableProducer( val jsMultiModuleCache = JsMultiModuleCache(caches) val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache() - val resolver = CrossModuleDependenciesResolver(cachedProgram.map { it.jsIrHeader }) + val resolver = CrossModuleDependenciesResolver(moduleKind, cachedProgram.map { it.jsIrHeader }) val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath) jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index dc49c491c2f..52095ddca29 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -159,7 +159,7 @@ class IrModuleToJsTransformer( val internalModuleName = ReservedJsNames.makeInternalModuleName() val globalNames = NameTable(namer.globalNames) val exportStatements = ExportModelToJsStatements(staticContext) { globalNames.declareFreshName(it, it) } - .generateModuleExport(exportedModule, internalModuleName) + .generateModuleExport(exportedModule, internalModuleName, false) val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it), false) }) val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt index efbfbf96af2..6b9a0fa02c9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt @@ -94,6 +94,7 @@ class IrModuleToJsTransformerTmp( private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! + private val isEsModules = moduleKind == ModuleKind.ES private val sourceMapInfo = SourceMapsInfo.from(backendContext.configuration) private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List>>) @@ -103,7 +104,7 @@ class IrModuleToJsTransformerTmp( } private fun associateIrAndExport(modules: Iterable): List { - val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) + val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules) return modules.map { module -> val files = module.files.map { file -> @@ -166,7 +167,7 @@ class IrModuleToJsTransformerTmp( } fun generateBinaryAst(files: Collection, allModules: Collection): List { - val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) + val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules) val exportData = files.map { it to exportModelGenerator.generateExportWithExternals(it) } @@ -235,12 +236,13 @@ class IrModuleToJsTransformerTmp( polyfills.statements += backendContext.polyfills.getAllPolyfillsFor(file) } - val internalModuleName = ReservedJsNames.makeInternalModuleName() + val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules } val globalNames = NameTable(globalNameScope) val exportStatements = ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport( ExportedModule(mainModuleName, moduleKind, exports), internalModuleName, + isEsModules ) result.exports.statements += exportStatements @@ -379,7 +381,7 @@ private fun generateWrappedModuleBody( // mutable container allows explicitly remove elements from itself, // so we are able to help GC to free heavy JsIrModule objects // TODO: It makes sense to invent something better, because this logic can be easily broken - val moduleToRef = program.asCrossModuleDependencies(relativeRequirePath).toMutableList() + val moduleToRef = program.asCrossModuleDependencies(moduleKind, relativeRequirePath).toMutableList() val mainModule = moduleToRef.removeLast().let { (main, mainRef) -> generateSingleWrappedModuleBody( mainModuleName, @@ -433,7 +435,7 @@ fun generateSingleWrappedModuleBody( sourceMapsInfo: SourceMapsInfo?, generateScriptModule: Boolean, generateCallToMain: Boolean, - crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty, + crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty(moduleKind), outJsProgram: Boolean = true ): CompilationOutputs { val program = Merger( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt index fd886263a85..4fd6c81cfcc 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.ir.backend.js.utils.toJsIdentifier import org.jetbrains.kotlin.js.backend.ast.* import java.io.File +import org.jetbrains.kotlin.serialization.js.ModuleKind class JsIrProgramFragment(val packageFqn: String) { val nameBindings = mutableMapOf() @@ -57,8 +58,8 @@ class JsIrModuleHeader( } class JsIrProgram(private var modules: List) { - fun asCrossModuleDependencies(relativeRequirePath: Boolean): List> { - val resolver = CrossModuleDependenciesResolver(modules.map { it.makeModuleHeader() }) + fun asCrossModuleDependencies(moduleKind: ModuleKind, relativeRequirePath: Boolean): List> { + val resolver = CrossModuleDependenciesResolver(moduleKind, modules.map { it.makeModuleHeader() }) modules = emptyList() val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath) return crossModuleReferences.entries.map { @@ -75,9 +76,12 @@ class JsIrProgram(private var modules: List) { } } -class CrossModuleDependenciesResolver(private val headers: List) { +class CrossModuleDependenciesResolver( + private val moduleKind: ModuleKind, + private val headers: List +) { fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map { - val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) } + val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(moduleKind, it, relativeRequirePath) } val definitionModule = mutableMapOf() val mainModuleHeader = headers.last() @@ -110,7 +114,11 @@ private fun String.prettyTag() = takeWhile { c -> c != '|' } private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder, val tag: String) -private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHeader, val relativeRequirePath: Boolean) { +private class JsIrModuleCrossModuleReferecenceBuilder( + val moduleKind: ModuleKind, + val header: JsIrModuleHeader, + val relativeRequirePath: Boolean +) { val imports = mutableListOf() val exports = mutableSetOf() var transitiveJsExportFrom = emptyList() @@ -155,7 +163,13 @@ private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHead val transitiveExport = transitiveJsExportFrom.mapNotNull { if (it.hasJsExports) import(it) else null } - return CrossModuleReferences(importedModules.values.toList(), transitiveExport, exportNames, resultImports) + return CrossModuleReferences( + moduleKind, + importedModules.values.toList(), + transitiveExport, + exportNames, + resultImports + ) } private fun relativeRequirePath(moduleHeader: JsIrModuleHeader): String? { @@ -177,6 +191,7 @@ private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHead class CrossModuleImport(val exportedAs: String, val moduleExporter: JsName) class CrossModuleReferences( + val moduleKind: ModuleKind, val importedModules: List, // additional Kotlin imported modules val transitiveJsExportFrom: List, // the list of modules which provide their js exports for transitive export val exports: Map, // tag -> index @@ -190,12 +205,21 @@ class CrossModuleReferences( val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value } jsImports = imports.entries.associate { val importedAs = tagToName[it.key] ?: error("Internal error: cannot find imported name for symbol ${it.key.prettyTag()}") - val exportRef = JsNameRef(it.value.exportedAs, ReservedJsNames.makeCrossModuleNameRef(it.value.moduleExporter)) + val exportRef = JsNameRef( + it.value.exportedAs, + it.value.moduleExporter.let { + if (moduleKind == ModuleKind.ES) { + it.makeRef() + } else { + ReservedJsNames.makeCrossModuleNameRef(it) + } + } + ) it.key to JsVars.JsVar(importedAs, exportRef) } } companion object { - val Empty = CrossModuleReferences(listOf(), emptyList(), emptyMap(), emptyMap()) + fun Empty(moduleKind: ModuleKind) = CrossModuleReferences(moduleKind, listOf(), emptyList(), emptyMap(), emptyMap()) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt index e894b092b34..844982cd92c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.utils.DFS +import org.jetbrains.kotlin.utils.addToStdlib.partitionIsInstance class Merger( private val moduleName: String, @@ -20,6 +21,7 @@ class Merger( private val generateCallToMain: Boolean, ) { + private val isEsModules = moduleKind == ModuleKind.ES private val importStatements = mutableMapOf() private val importedModulesMap = mutableMapOf() @@ -65,16 +67,25 @@ class Merger( if (crossModuleReferences.exports.isNotEmpty()) { val internalModuleName = ReservedJsNames.makeInternalModuleName() - val createExportBlock = jsAssignment( - ReservedJsNames.makeCrossModuleNameRef(internalModuleName), - JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral()) - ).makeStmt() - additionalExports += createExportBlock + if (isEsModules) { + val exportedElements = crossModuleReferences.exports.entries.map { (tag, hash) -> + val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'") + JsExport.Element(internalName, JsName(hash, false)) + } - crossModuleReferences.exports.entries.forEach { (tag, hash) -> - val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'") - val crossModuleRef = ReservedJsNames.makeCrossModuleNameRef(ReservedJsNames.makeInternalModuleName()) - additionalExports += jsAssignment(JsNameRef(hash, crossModuleRef), JsNameRef(internalName)).makeStmt() + additionalExports += JsExport(JsExport.Subject.Elements(exportedElements)) + } else { + val createExportBlock = jsAssignment( + ReservedJsNames.makeCrossModuleNameRef(internalModuleName), + JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral()) + ).makeStmt() + additionalExports += createExportBlock + + crossModuleReferences.exports.entries.forEach { (tag, hash) -> + val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'") + val crossModuleRef = ReservedJsNames.makeCrossModuleNameRef(ReservedJsNames.makeInternalModuleName()) + additionalExports += jsAssignment(JsNameRef(hash, crossModuleRef), JsNameRef(internalName)).makeStmt() + } } } } @@ -125,25 +136,41 @@ class Merger( } private fun declareAndCallJsExporter(): List { - val exportBody = JsBlock(fragments.flatMap { it.exports.statements }) - if (exportBody.isEmpty) { - return emptyList() - } + if (isEsModules) { + val allExportRelatedStatements = fragments.flatMap { it.exports.statements } + val (allExportStatements, restStatements) = allExportRelatedStatements.partitionIsInstance() + val (currentModuleExportStatements, restExportStatements) = allExportStatements.partition { it.fromModule == null } + val exportedElements = currentModuleExportStatements.takeIf { it.isNotEmpty() } + ?.asSequence() + ?.flatMap { (it.subject as JsExport.Subject.Elements).elements } + ?.distinctBy { (it.alias ?: it.name).ident } + ?.map { if (it.name.ident == it.alias?.ident) JsExport.Element(it.name, null) else it } + ?.toList() - val internalModuleName = ReservedJsNames.makeInternalModuleName() - val exporterName = ReservedJsNames.makeJsExporterName() - val jsExporterFunction = JsFunction(emptyScope, "js exporter function").apply { - body = exportBody - name = exporterName - parameters.add(JsParameter(internalModuleName)) + val oneLargeExportStatement = exportedElements?.let { JsExport(JsExport.Subject.Elements(it)) } + + return restStatements + listOfNotNull(oneLargeExportStatement) + restExportStatements + } else { + val exportBody = JsBlock(fragments.flatMap { it.exports.statements }) + if (exportBody.isEmpty) { + return emptyList() + } + + val internalModuleName = ReservedJsNames.makeInternalModuleName() + val exporterName = ReservedJsNames.makeJsExporterName() + val jsExporterFunction = JsFunction(emptyScope, "js exporter function").apply { + body = exportBody + name = exporterName + parameters.add(JsParameter(internalModuleName)) + } + val jsExporterCall = JsInvocation(exporterName.makeRef(), internalModuleName.makeRef()) + val result = mutableListOf(jsExporterFunction.makeStmt(), jsExporterCall.makeStmt()) + if (!generateCallToMain) { + val exportExporter = jsAssignment(JsNameRef(exporterName, internalModuleName.makeRef()), exporterName.makeRef()) + result += exportExporter.makeStmt() + } + return result } - val jsExporterCall = JsInvocation(exporterName.makeRef(), internalModuleName.makeRef()) - val result = mutableListOf(jsExporterFunction.makeStmt(), jsExporterCall.makeStmt()) - if (!generateCallToMain) { - val exportExporter = jsAssignment(JsNameRef(exporterName, internalModuleName.makeRef()), exporterName.makeRef()) - result += exportExporter.makeStmt() - } - return result } private fun transitiveJsExport(): List { @@ -214,9 +241,6 @@ class Merger( if (generateScriptModule) { with(program.globalBlock) { - if (!generateScriptModule) { - statements += JsStringLiteral("use strict").makeStmt() - } statements.addWithComment("block: polyfills", polyfillDeclarationBlock.statements) statements.addWithComment("block: imports", importStatements) statements += moduleBody @@ -228,7 +252,7 @@ class Merger( parameters += JsParameter(internalModuleName) parameters += (importedJsModules).map { JsParameter(it.internalName) } with(body) { - if (!generateScriptModule) { + if (!isEsModules) { statements += JsStringLiteral("use strict").makeStmt() } statements.addWithComment("block: imports", importStatements) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt index 25b43ebf311..d8f844cfe94 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt @@ -17,7 +17,7 @@ object ModuleWrapperTranslation { } fun wrap( - moduleId: String, function: JsExpression, importedModules: List, + moduleId: String, function: JsFunction, importedModules: List, program: JsProgram, kind: ModuleKind ): List { return when (kind) { @@ -25,7 +25,7 @@ object ModuleWrapperTranslation { ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program) ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program) ModuleKind.PLAIN -> wrapPlain(moduleId, function, importedModules, program) - ModuleKind.ES -> error("ES modules are not supported in legacy wrapper") + ModuleKind.ES -> wrapEsModule(function, importedModules) } } @@ -100,6 +100,21 @@ object ModuleWrapperTranslation { return listOf(invocation.makeStmt()) } + private fun wrapEsModule(function: JsFunction, importedModules: List): List { + val importStatements = importedModules.zip(function.parameters.drop(1)).map { + JsImport( + it.first.externalName, + if (it.first.plainReference == null) { + JsImport.Target.All(alias = it.second.name) + } else { + JsImport.Target.Default(name = it.second.name) + } + ) + } + return importStatements + function.body.statements.dropLast(1) + } + + private fun wrapPlain( moduleId: String, function: JsExpression, importedModules: List, program: JsProgram diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NewNamer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NewNamer.kt deleted file mode 100644 index 9b6e9067428..00000000000 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NewNamer.kt +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.ir.backend.js.utils - -import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext -import org.jetbrains.kotlin.ir.backend.js.codegen.IrToJs -import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference -import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid -import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlin.js.backend.ast.JsImport -import org.jetbrains.kotlin.js.backend.ast.JsName - -class StaticDeclarationNumerator { - var currentNumber = 0 - val numeration = mutableMapOf() - - fun add(moduleFragment: IrModuleFragment) { - moduleFragment.files.forEach { add(it) } - } - - fun add(declaration: IrDeclaration) { - // TODO: We should not visit declarations multiple times. - // Investigate enum tests in dce-driven mode. - if (declaration !in numeration) { - numeration[declaration] = currentNumber - currentNumber++ - } - } - - fun add(packageFragment: IrPackageFragment) { - packageFragment.acceptChildrenVoid(object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitDeclaration(declaration: IrDeclarationBase) { - if (declaration !is IrVariable) { - add(declaration) - } - super.visitDeclaration(declaration) - } - }) - } -} - -class NewStableStaticNamesCollectorVisitor(val needToCollectReferences: Boolean) : IrElementVisitorVoid { - val collectedStableNames = mutableSetOf() - - init { - collectedStableNames.addAll(RESERVED_IDENTIFIERS) - collectedStableNames.add(Namer.IMPLICIT_RECEIVER_NAME) - } - - private fun IrDeclaration.collectStableName() { - collectedStableNames += stableNameForExternalDeclaration(this) ?: return - } - - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitDeclaration(declaration: IrDeclarationBase) { - super.visitDeclaration(declaration) - declaration.collectStableName() - } - - override fun visitDeclarationReference(expression: IrDeclarationReference) { - super.visitDeclarationReference(expression) - if (needToCollectReferences) { - val declaration = expression.symbol.owner as? IrDeclaration - declaration?.collectStableName() - } - } -} - -class NewNamerImpl( - val context: JsIrBackendContext, - val unit: IrToJs.CodegenUnit, - val exportId: (IrDeclarationWithName) -> String, - stableNames: Set, -) : IrNamerBase() { - val staticNames = NameTable( - reserved = stableNames.toMutableSet() - ) - val internalImports = mutableMapOf() - - override fun getNameForMemberFunction(function: IrSimpleFunction): JsName { - require(function.dispatchReceiverParameter != null) - val name = jsFunctionSignature(function, context) - return name.toJsName() - } - - override fun getNameForMemberField(field: IrField): JsName { - val fieldName = sanitizeName( - try { - exportId(field) - } catch (e: IllegalStateException) { - // TODO: Fix DCE with inline classes and remove this hack - field.name.asString() + "_LIKELY_ELIMINATED_BY_DCE" - } - ) - // TODO: Webpack not minimize member names, it is long name, which is not minimized, so it affects final JS bundle size - // Use shorter names - return JsName("f_$fieldName", false) - } - - override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName { - staticNames.names[declaration]?.let { return JsName(it, false) } - - fun registerImport(moduleId: String, importedName: String) { - val fullModuleId = if (moduleId.startsWith(".")) { - unit.pathToKotlinModulesRoot + moduleId - } else { - // TODO: Do we cover this path in tests? - moduleId - } - - val import = internalImports.getOrPut(fullModuleId) { - JsImport(fullModuleId) - } - import.elements += JsImport.Element(importedName, staticNames.names[declaration]!!) - } - - if (declaration.isEffectivelyExternal()) { - val jsModule: String? = declaration.getJsModule() - val maybeParentFile: IrFile? = declaration.parent as? IrFile - val fileJsModule: String? = maybeParentFile?.getJsModule() - val jsQualifier: String? = maybeParentFile?.getJsQualifier() - - when { - jsModule != null -> { - // TODO: Support jsQualifier - staticNames.declareFreshName(declaration, declaration.name.asString()) - registerImport(jsModule, "default") - } - - fileJsModule != null -> { - // TODO: Support jsQualifier - staticNames.declareFreshName(declaration, declaration.name.asString()) - registerImport(fileJsModule, declaration.getJsNameOrKotlinName().identifier) - } - - else -> { - var name = declaration.getJsNameOrKotlinName().identifier - if (jsQualifier != null) - name = "$jsQualifier.$name" - - staticNames.declareStableName(declaration, name) - } - } - - - } else { // Non-external declaration - val name = declaration.nameIfPropertyAccessor() ?: declaration.name.asString() - staticNames.declareFreshName(declaration, name) - val unitReference = unit.referenceCodegenUnitOfDeclaration(declaration) - if (unitReference is IrToJs.OtherUnitReference) { - registerImport(unitReference.importPath, exportId(declaration)) - } - } - - return JsName(staticNames.names[declaration]!!, false) - } -} - -// TODO: Cache? -private fun stableNameForExternalDeclaration(declaration: IrDeclaration): String? { - if (declaration !is IrDeclarationWithName || - !declaration.hasStaticDispatch() || - !declaration.isEffectivelyExternal() || - declaration.isPropertyAccessor || - declaration.isPropertyField - ) { - return null - } - - if (declaration is IrConstructor) { - return stableNameForExternalDeclaration(declaration.parentAsClass) - } - - val importedFromModuleOnly = - declaration.getJsModule() != null && !declaration.isJsNonModule() - - val jsName = declaration.getJsName() - - val jsQualifier = declaration.fileOrNull?.getJsQualifier() - - return when { - importedFromModuleOnly -> - null - - jsQualifier != null -> - jsQualifier.split('1')[0] - - jsName != null -> - jsName - - else -> - declaration.name.identifier - } -} - diff --git a/compiler/testData/cli/js/jsHelp.out b/compiler/testData/cli/js/jsHelp.out index 8743f2a583b..0343885b6af 100644 --- a/compiler/testData/cli/js/jsHelp.out +++ b/compiler/testData/cli/js/jsHelp.out @@ -3,7 +3,7 @@ where possible options include: -libraries Paths to Kotlin libraries with .meta.js and .kjsm files, separated by system path separator -main {call|noCall} Define whether the `main` function should be called upon execution -meta-info Generate .meta.js and .kjsm files with metadata. Use to create a library - -module-kind {plain|amd|commonjs|umd} + -module-kind {plain|amd|commonjs|umd|es} Kind of the JS module generated by the compiler -no-stdlib Don't automatically include the default Kotlin/JS stdlib into compilation dependencies -output Destination *.js file for the compilation result diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt index ae8d30b52c8..0546cc3144a 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt @@ -34,8 +34,6 @@ object BinaryArtifacts { class JsIrArtifact(override val outputFile: File, val compilerResult: CompilerResult, val icCache: Map? = null) : Js() - class JsEsArtifact(override val outputFile: File, val outputDceFile: File?) : Js() - data class IncrementalJsArtifact(val originalArtifact: Js, val recompiledArtifact: Js) : Js() { override val outputFile: File get() = unwrap().outputFile diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java index f15ff5fd658..2d5318478f0 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java @@ -1329,31 +1329,47 @@ public class JsToStringGenerationVisitor extends JsVisitor { @Override public void visitImport(@NotNull JsImport jsImport) { - p.print("import {"); - boolean isMultiline = jsImport.getElements().size() > 1; - p.indentIn(); - if (isMultiline) - newlineOpt(); - else - space(); + JsImport.Target target = jsImport.getTarget(); - for (JsImport.Element element : jsImport.getElements()) { - nameDef(element.getName()); - JsName alias = element.getAlias(); - if (alias != null) { - p.print(" as "); - nameDef(alias); - } + p.print("import "); - if (isMultiline) { - p.print(','); + if (target instanceof JsImport.Target.Default) { + nameDef(((JsImport.Target.Default) target).getName()); + } else if (target instanceof JsImport.Target.All) { + p.print("* as "); + nameDef(((JsImport.Target.All) target).getAlias()); + } else if (target instanceof JsImport.Target.Elements) { + List elements = ((JsImport.Target.Elements) target).getElements(); + + p.print("{"); + boolean isMultiline = elements.size() > 1; + p.indentIn(); + if (isMultiline) newlineOpt(); - } else { + else space(); + + for (JsImport.Element element : elements) { + nameDef(element.getName()); + JsName alias = element.getAlias(); + if (alias != null) { + p.print(" as "); + nameDef(alias); + } + + if (isMultiline) { + p.print(','); + newlineOpt(); + } + else { + space(); + } } + p.indentOut(); + p.print("}"); } - p.indentOut(); - p.print("} from "); + + p.print(" from "); p.print(javaScriptString(jsImport.getModule())); } diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt index 412a856782c..f388188ace3 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt @@ -7,8 +7,23 @@ package org.jetbrains.kotlin.js.backend.ast class JsImport( val module: String, - val elements: MutableList = mutableListOf(), + val target: Target, ) : SourceInfoAwareJsNode(), JsStatement { + constructor(module: String, elements: MutableList = mutableListOf()) : this(module, Target.Elements(elements)) + + val elements: MutableList + get() = (target as Target.Elements).elements + + sealed class Target { + class Elements(val elements: MutableList) : Target() + class Default(val name: JsName) : Target() { + constructor(name: String) : this(JsName(name, false)) + } + + class All(val alias: JsName) : Target() { + constructor(alias: String) : this(JsName(alias, false)) + } + } class Element( val name: JsName, @@ -25,7 +40,7 @@ class JsImport( } override fun deepCopy(): JsStatement = - JsImport(module, elements.map { it }.toMutableList()) + JsImport(module, target) override fun traverse(v: JsVisitorWithContext, ctx: JsContext<*>) { v.visit(this, ctx) diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt index 4cf663f5e37..6eb60c837a0 100644 --- a/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/ProcessBasedScriptEngine.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.util.text.StringUtil private val LINE_SEPARATOR = System.getProperty("line.separator")!! private val END_MARKER = "$LINE_SEPARATOR" +private val ESM_EXTENSION = ".mjs" abstract class ProcessBasedScriptEngine( private val executablePath: String @@ -58,6 +59,7 @@ abstract class ProcessBasedScriptEngine( } override fun loadFile(path: String) { + if (path.endsWith(ESM_EXTENSION)) return eval("load('${path.replace('\\', '/')}');") } diff --git a/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js b/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js index 87f5158e964..de96f66b2ed 100644 --- a/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js +++ b/js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js @@ -75,27 +75,31 @@ function restoreGlobalState() { resetRealm(); // noinspection InfiniteLoopJS -while (true) { - let code = readline().replace(/\\n/g, '\n'); +async function loop() { + while (true) { + let code = readline().replace(/\\n/g, '\n'); - try { - switch (code) { - case "!reset": - resetRealm() - break; - case "!saveGlobalState": - saveGlobalState(); - break; - case "!restoreGlobalState": - restoreGlobalState(); - break; - default: - print(Realm.eval(currentRealmIndex, code)); + try { + switch (code) { + case "!reset": + resetRealm() + break; + case "!saveGlobalState": + saveGlobalState(); + break; + case "!restoreGlobalState": + restoreGlobalState(); + break; + default: + print(await Realm.eval(currentRealmIndex, code)); + } + } catch(e) { + printErr(e.stack != null ? e.stack : e.toString()); + printErr('\nCODE:\n' + code); } - } catch(e) { - printErr(e.stack != null ? e.stack : e.toString()); - printErr('\nCODE:\n' + code); - } - print(''); + print(''); + } } + +loop() \ No newline at end of file diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/JavaScript.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/JavaScript.kt index 20e942d54e0..9eab0783a6b 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/JavaScript.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/JavaScript.kt @@ -23,4 +23,7 @@ object JavaScript { const val EXTENSION = "js" const val DOT_EXTENSION = "." + EXTENSION + + const val MODULE_EXTENSION = "mjs" + const val DOT_MODULE_EXTENSION = "." + MODULE_EXTENSION } diff --git a/js/js.tests/test/org/jetbrains/kotlin/integration/AntTaskJsTest.java b/js/js.tests/test/org/jetbrains/kotlin/integration/AntTaskJsTest.java index 60b9acfb5c2..5ef3d2e1ea0 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/integration/AntTaskJsTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/integration/AntTaskJsTest.java @@ -49,7 +49,7 @@ public class AntTaskJsTest extends AbstractAntTaskTest { List filePaths = CollectionsKt.map(fileNames, s -> getOutputFileByName(s).getAbsolutePath()); - (useNashorn ? NashornJsTestChecker.INSTANCE : V8JsTestChecker.INSTANCE).check(filePaths, "out", "foo", "box", "OK", withModuleSystem); + (useNashorn ? NashornJsTestChecker.INSTANCE : V8JsTestChecker.INSTANCE).check(filePaths, "out", "foo", "box", "OK", withModuleSystem, null); } private void doJsAntTestForPostfixPrefix(@Nullable String prefix, @Nullable String postfix) throws Exception { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/ClassicJsBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/ClassicJsBackendFacade.kt index 126b5ab1932..595b3e01168 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/ClassicJsBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/ClassicJsBackendFacade.kt @@ -51,9 +51,7 @@ class ClassicJsBackendFacade( "$KOTLIN_TEST_INTERNAL.setModuleId(\"$escapedModuleId\"); }\n" + "$content\n" - ModuleKind.PLAIN -> content - - ModuleKind.ES -> error("Module emulation markers are not supported for ES modules") + ModuleKind.PLAIN, ModuleKind.ES -> content } } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt index 20cb068f669..e0c72b12318 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt @@ -12,36 +12,29 @@ import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureDe import org.jetbrains.kotlin.cli.common.isWindows import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.ir.backend.js.* -import org.jetbrains.kotlin.ir.backend.js.codegen.CompilerOutputSink import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity -import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationOptions -import org.jetbrains.kotlin.ir.backend.js.codegen.generateEsModules -import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.irMessageLogger import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.test.handlers.JsBoxRunner.Companion.TEST_FUNCTION -import org.jetbrains.kotlin.js.test.utils.esModulesSubDir import org.jetbrains.kotlin.js.test.utils.extractTestPackage import org.jetbrains.kotlin.js.test.utils.jsIrIncrementalDataProvider import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration -import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.test.DebugMode import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives -import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives import org.jetbrains.kotlin.test.frontend.classic.moduleDescriptorProvider import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.services.* @@ -49,6 +42,9 @@ import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurato import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull import java.io.File +const val REGULAR_EXTENSION = ".js" +const val ESM_EXTENSION = ".mjs" + class JsIrBackendFacade( val testServices: TestServices, private val firstTimeCompilation: Boolean @@ -91,16 +87,16 @@ class JsIrBackendFacade( else -> JsGenerationGranularity.WHOLE_PROGRAM } - val testPackage = extractTestPackage(testServices) + val testPackage = extractTestPackage(testServices, ignoreEsModules = false) val skipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE in module.directives if (skipRegularMode) return null if (JsEnvironmentConfigurator.incrementalEnabled(testServices)) { val outputFile = if (firstTimeCompilation) { - File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js") + File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + module.kind.extension) } else { - File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name) + ".js") + File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name) + module.kind.extension) } val compiledModule = CompilerResult( @@ -136,11 +132,12 @@ class JsIrBackendFacade( PhaseConfig(jsPhases) } + val loweredIr = compileIr( - irModuleFragment, + irModuleFragment.apply { resolveTestPathes() }, MainModule.Klib(inputArtifact.outputFile.absolutePath), configuration, - dependencyModules, + dependencyModules.apply { forEach { it.resolveTestPathes() } }, emptyMap(), irModuleFragment.irBuiltins, symbolTable, @@ -164,22 +161,27 @@ class JsIrBackendFacade( module: TestModule, loweredIr: LoweredIr, granularity: JsGenerationGranularity, - ): BinaryArtifacts.Js? { - val generateDts = JsEnvironmentConfigurationDirectives.GENERATE_DTS in module.directives + ): BinaryArtifacts.Js { val mainArguments = JsEnvironmentConfigurator.getMainCallParametersForModule(module) .run { if (shouldBeGenerated()) arguments() else null } val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in module.directives val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in module.directives - val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in module.directives val runNewIr2Js = JsEnvironmentConfigurationDirectives.RUN_NEW_IR_2_JS in module.directives val perModuleOnly = JsEnvironmentConfigurationDirectives.SPLIT_PER_MODULE in module.directives + val isEsModules = JsEnvironmentConfigurationDirectives.ES_MODULES in module.directives || + module.directives[JsEnvironmentConfigurationDirectives.MODULE_KIND].contains(ModuleKind.ES) - val outputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, TranslationMode.FULL) + ".js") - val dceOutputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, TranslationMode.FULL_DCE_MINIMIZED_NAMES) + ".js") - if (!esModules) { - if (runNewIr2Js) { - val transformer = IrModuleToJsTransformerTmp(loweredIr.context, mainArguments) + val outputFile = File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, TranslationMode.FULL) + module.kind.extension) + if (runNewIr2Js) { + val transformer = IrModuleToJsTransformerTmp( + loweredIr.context, + mainArguments, + moduleToName = JsIrModuleToPath( + testServices, + isEsModules && granularity != JsGenerationGranularity.WHOLE_PROGRAM + ) + ) // If runIrDce then include DCE results // If perModuleOnly then skip whole program // (it.dce => runIrDce) && (perModuleOnly => it.perModule) @@ -199,19 +201,14 @@ class JsIrBackendFacade( relativeRequirePath = false ) - return BinaryArtifacts.Js.JsIrArtifact(outputFile, transformer.generateModule(loweredIr.allModules)).dump(module) - } + return BinaryArtifacts.Js.JsIrArtifact(outputFile, transformer.generateModule(loweredIr.allModules)).dump(module) } + } - val options = JsGenerationOptions(generatePackageJson = true, generateTypeScriptDefinitions = generateDts) - generateEsModules(loweredIr, jsOutputSink(outputFile.parentFile.esModulesSubDir), mainArguments, granularity, options) - - if (runIrDce) { - eliminateDeadDeclarations(loweredIr.allModules, loweredIr.context) - generateEsModules(loweredIr, jsOutputSink(dceOutputFile.parentFile.esModulesSubDir), mainArguments, granularity, options) - return BinaryArtifacts.Js.JsEsArtifact(outputFile, dceOutputFile).dump(module) + private fun IrModuleFragment.resolveTestPathes() { + JsIrPathReplacer(testServices).let { + files.forEach(it::lower) } - return BinaryArtifacts.Js.JsEsArtifact(outputFile, null).dump(module) } private fun loadIrFromKlib(module: TestModule, configuration: CompilerConfiguration): IrModuleInfo { @@ -238,55 +235,10 @@ class JsIrBackendFacade( ) { if (it == mainModuleLib) moduleDescriptor else testServices.jsLibraryProvider.getDescriptorByCompiledLibrary(it) } } - private fun loadIrFromSources( + private fun BinaryArtifacts.Js.JsIrArtifact.dump( module: TestModule, - configuration: CompilerConfiguration, - inputArtifact: ClassicFrontendOutputArtifact - ): IrModuleInfo { - val errorPolicy = configuration.get(JSConfigurationKeys.ERROR_TOLERANCE_POLICY) ?: ErrorTolerancePolicy.DEFAULT - val messageLogger = configuration.irMessageLogger - val symbolTable = SymbolTable(IdSignatureDescriptor(JsManglerDesc), IrFactoryImplForJsIC(WholeWorldStageController()),) - val verifySignatures = JsEnvironmentConfigurationDirectives.SKIP_MANGLE_VERIFICATION !in module.directives - - val psi2Ir = Psi2IrTranslator( - configuration.languageVersionSettings, - Psi2IrConfiguration(errorPolicy.allowErrors), - messageLogger::checkNoUnboundSymbols - ) - val psi2IrContext = psi2Ir.createGeneratorContext( - inputArtifact.analysisResult.moduleDescriptor, - inputArtifact.analysisResult.bindingContext, - symbolTable - ) - - return getIrModuleInfoForSourceFiles( - psi2IrContext, - inputArtifact.project, - configuration, - inputArtifact.allKtFiles.values.toList(), - sortDependencies(JsEnvironmentConfigurator.getAllRecursiveLibrariesFor(module, testServices)), - emptyMap(), - symbolTable, - messageLogger, - loadFunctionInterfacesIntoStdlib = true, - verifySignatures, - ) { testServices.jsLibraryProvider.getDescriptorByCompiledLibrary(it) } - } - - private fun jsOutputSink(perFileOutputDir: File): CompilerOutputSink { - perFileOutputDir.deleteRecursively() - perFileOutputDir.mkdirs() - - return object : CompilerOutputSink { - override fun write(module: String, path: String, content: String) { - val file = File(File(perFileOutputDir, module), path) - file.parentFile.mkdirs() - file.writeText(content) - } - } - } - - private fun BinaryArtifacts.Js.JsIrArtifact.dump(module: TestModule, firstTimeCompilation: Boolean = true): BinaryArtifacts.Js.JsIrArtifact { + firstTimeCompilation: Boolean = true + ): BinaryArtifacts.Js.JsIrArtifact { val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) val moduleId = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME) val moduleKind = configuration.get(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN) @@ -297,9 +249,15 @@ class JsIrBackendFacade( if (dontSkipRegularMode) { for ((mode, output) in compilerResult.outputs.entries) { val outputFile = if (firstTimeCompilation) { - File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, mode) + ".js") + File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, mode) + moduleKind.extension) } else { - File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name, mode) + ".js") + File( + JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath( + testServices, + module.name, + mode + ) + moduleKind.extension + ) } output.writeTo(outputFile, moduleId, moduleKind) } @@ -307,24 +265,13 @@ class JsIrBackendFacade( if (generateDts) { outputFile - .withReplacedExtensionOrNull("_v5.js", ".d.ts")!! + .withReplacedExtensionOrNull("_v5${moduleKind.extension}", ".d.ts")!! .write(compilerResult.tsDefinitions ?: error("No ts definitions")) } return this } - private fun BinaryArtifacts.Js.JsEsArtifact.dump(module: TestModule): BinaryArtifacts.Js.JsEsArtifact { - val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) - val moduleName = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME) - val esmTestFile = outputFile.parentFile.esModulesSubDir.resolve("test.mjs") - createEsTestFile(esmTestFile, moduleName) - - val dceEsmTestFile = outputDceFile?.parentFile?.esModulesSubDir?.resolve("test.mjs") ?: return this - createEsTestFile(dceEsmTestFile, moduleName) - return this - } - private fun CompilationOutputs.writeTo(outputFile: File, moduleId: String, moduleKind: ModuleKind) { val wrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, moduleId = moduleId, moduleKind = moduleKind) outputFile.write(wrappedCode) @@ -344,32 +291,37 @@ class JsIrBackendFacade( writeText(text) } - private fun createEsTestFile(file: File, moduleName: String) { - val customTestModule = testServices.moduleStructure.modules - .flatMap { it.files } - .singleOrNull { JsEnvironmentConfigurationDirectives.ENTRY_ES_MODULE in it.directives } - val customTestModuleText = customTestModule?.let { testServices.sourceFileProvider.getContentOfSourceFile(it) } - - val defaultTestModule = - """ - import { box } from './${moduleName}/index.js'; - let res = box(); - if (res !== "OK") { - throw "Wrong result: " + String(res); - } - """.trimIndent() - file.writeText(customTestModuleText ?: defaultTestModule) - } - override fun shouldRunAnalysis(module: TestModule): Boolean { return JsEnvironmentConfigurator.isMainModule(module, testServices) } } +val ModuleKind.extension: String + get() = when (this) { + ModuleKind.ES -> ESM_EXTENSION + else -> REGULAR_EXTENSION + } + +val RegisteredDirectives.moduleKind: ModuleKind + get() = get(JsEnvironmentConfigurationDirectives.MODULE_KIND).singleOrNull() + ?: if (contains(JsEnvironmentConfigurationDirectives.ES_MODULES)) ModuleKind.ES else ModuleKind.PLAIN + +val TestModule.kind: ModuleKind + get() = directives.moduleKind + fun String.augmentWithModuleName(moduleName: String): String { - check(endsWith("_v5.js")) - val normalizedName = moduleName.run { if (isWindows) minify() else this } - return removeSuffix("_v5.js") + "-${normalizedName}_v5.js" + return if (moduleName.isPath()) { + replaceAfterLast(File.separator, moduleName.replace("./", "")) + } else { + val suffix = when { + endsWith(ESM_EXTENSION) -> ESM_EXTENSION + endsWith(REGULAR_EXTENSION) -> REGULAR_EXTENSION + else -> error("Unexpected file '$this' extension") + } + val normalizedName = moduleName.run { if (isWindows) minify() else this } + + return removeSuffix("_v5$suffix") + "-${normalizedName}_v5$suffix" + } } // D8 ignores Windows settings related to extending of maximum path symbols count @@ -380,4 +332,6 @@ fun String.minify(): String { .replace("_minimal_for_test", "_min") } +private fun String.isPath(): Boolean = contains("/") + fun File.augmentWithModuleName(moduleName: String): File = File(absolutePath.augmentWithModuleName(moduleName)) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrModuleToPath.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrModuleToPath.kt new file mode 100644 index 00000000000..546ff67cc4d --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrModuleToPath.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.js.test.converters + +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeName +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator.Companion.getJsArtifactSimpleName +import org.jetbrains.kotlin.utils.addToStdlib.runIf + +private typealias K = IrModuleFragment +private typealias V = String + +class JsIrModuleToPath(val testServices: TestServices, shouldProvidePaths: Boolean) : Map { + override val size = if (!shouldProvidePaths) 0 else 1 + override val entries = emptySet>() + override val keys = emptySet() + override val values = emptyList() + + override fun isEmpty() = size == 0 + override fun containsKey(key: K): Boolean = !isEmpty() + override fun containsValue(value: V): Boolean = !isEmpty() + + override operator fun get(key: K): V? { + return runIf(!isEmpty()) { + "./${getJsArtifactSimpleName(testServices, key.safeName)}_v5.mjs" + } + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt new file mode 100644 index 00000000000..db5b03edb8e --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.js.test.converters + +import org.jetbrains.kotlin.backend.common.DeclarationTransformer +import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations +import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.util.getAnnotation +import org.jetbrains.kotlin.js.test.utils.* +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.isJsFile +import org.jetbrains.kotlin.test.services.isMjsFile +import org.jetbrains.kotlin.test.services.moduleStructure + +class JsIrPathReplacer(testServices: TestServices) : DeclarationTransformer { + private val replacements = testServices.collectReplacementsMap() + + override fun lower(irFile: IrFile) { + super.lower(irFile) + irFile.replaceJsModulePath() + } + + override fun transformFlat(declaration: IrDeclaration): List? { + return null.also { + declaration.replaceJsModulePath() + } + } + + private fun IrAnnotationContainer.replaceJsModulePath() { + val jsModuleAnnotation = getAnnotation(JsAnnotations.jsModuleFqn) ?: return + @Suppress("UNCHECKED_CAST") + val stringLiteral = jsModuleAnnotation.getValueArgument(0) as IrConst + val pathReplacement = stringLiteral.getReplacement() ?: return + + jsModuleAnnotation.putValueArgument(0, pathReplacement) + } + + private fun IrConst.getReplacement(): IrConst? { + val replacement = replacements[value] ?: replacements[value.replace("./", "")] ?: return null + return IrConstImpl.string(startOffset, endOffset, type, "./" + replacement.replace("./", "")) + } + + private fun TestServices.collectReplacementsMap(): Map { + return moduleStructure.modules.asSequence() + .map { module -> module to module.files.filter { it.isJsFile || it.isMjsFile } } + .filter { (_, files) -> files.isNotEmpty() } + .flatMap { (module, files) -> files.map { it.relativePath to module.getNameFor(it, this) } } + .plus(getAdditionalFiles(this).map { it.name to it.name }) + .plus(getAdditionalMainFiles(this).map { it.name to it.name }) + .toMap() + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsBoxRunner.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsBoxRunner.kt index e4db3930c57..1908f3282d7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsBoxRunner.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsBoxRunner.kt @@ -5,25 +5,15 @@ package org.jetbrains.kotlin.js.test.handlers -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode -import org.jetbrains.kotlin.js.testOld.engines.ExternalTool import org.jetbrains.kotlin.js.test.utils.* import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives import org.jetbrains.kotlin.test.services.TestServices -import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator import org.jetbrains.kotlin.test.services.defaultsProvider import org.jetbrains.kotlin.test.services.moduleStructure -import java.io.File - -private val v8tool by lazy { ExternalTool(System.getProperty("javascript.engine.path.V8")) } class JsBoxRunner(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) { override fun processAfterAllModules(someAssertionWasFailed: Boolean) { - if (someAssertionWasFailed) return - - if (JsEnvironmentConfigurationDirectives.ES_MODULES in testServices.moduleStructure.allDirectives) { - runEsCode() - } else { + if (!someAssertionWasFailed) { runJsCode() } } @@ -43,37 +33,30 @@ class JsBoxRunner(testServices: TestServices) : AbstractJsArtifactsCollector(tes val dontSkipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE !in globalDirectives if (dontSkipRegularMode) { - for (jsFiles in allJsFiles.values) { - runGeneratedCode(jsFiles, testModuleName, testPackage, withModuleSystem) + for ((mode, jsFiles) in allJsFiles) { + val entryModulePath = extractEntryModulePath(mode, testServices) + runGeneratedCode(entryModulePath, jsFiles, testModuleName, testPackage, withModuleSystem) } } } - private fun runEsCode() { - val globalDirectives = testServices.moduleStructure.allDirectives - - val esmOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices).esModulesSubDir - val esmDceOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_DCE_MINIMIZED_NAMES).esModulesSubDir - - val dontSkipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE !in globalDirectives - val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in globalDirectives - if (dontSkipRegularMode) { - singleRunEsCode(esmOutputDir) - if (runIrDce) { - singleRunEsCode(esmDceOutputDir) - } - } - } - - private fun singleRunEsCode(esmOutputDir: File) { - val perFileEsModuleFile = "$esmOutputDir/test.mjs" - val (allNonEsModuleFiles, inputJsFilesAfter) = extractAllFilesForEsRunner(testServices, esmOutputDir) - v8tool.run(*allNonEsModuleFiles.toTypedArray(), perFileEsModuleFile, *inputJsFilesAfter.toTypedArray()) - } - - private fun runGeneratedCode(jsFiles: List, testModuleName: String?, testPackage: String?, withModuleSystem: Boolean) { + private fun runGeneratedCode( + entryModulePath: String?, + jsFiles: List, + testModuleName: String?, + testPackage: String?, + withModuleSystem: Boolean + ) { getTestChecker(testServices) - .check(jsFiles, testModuleName, testPackage, TEST_FUNCTION, DEFAULT_EXPECTED_RESULT, withModuleSystem) + .check( + jsFiles, + testModuleName, + testPackage, + TEST_FUNCTION, + DEFAULT_EXPECTED_RESULT, + withModuleSystem, + entryModulePath, + ) } companion object { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDtsHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDtsHandler.kt index 63eb5d8c681..59aa3e83de3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDtsHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDtsHandler.kt @@ -24,6 +24,7 @@ class JsDtsHandler(testServices: TestServices) : JsBinaryArtifactHandler(testSer val referenceDtsFile = module.files.first().originalFile.withReplacedExtensionOrNull(".kt", ".d.ts") ?: error("Can't find reference .d.ts file") val generatedDtsFile = info.outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts") + ?: info.outputFile.withReplacedExtensionOrNull("_v5.mjs", ".d.ts") ?: error("Can't find generated .d.ts file") val generatedDts = generatedDtsFile.readText() diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt index 0bc11633cab..cfc8a9a3791 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt @@ -10,9 +10,10 @@ import org.jetbrains.kotlin.js.JavaScript import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.test.JsAdditionalSourceProvider import org.jetbrains.kotlin.js.test.converters.augmentWithModuleName +import org.jetbrains.kotlin.js.test.converters.extension +import org.jetbrains.kotlin.js.test.converters.kind import org.jetbrains.kotlin.js.test.handlers.JsBoxRunner.Companion.TEST_FUNCTION import org.jetbrains.kotlin.js.testOld.* -import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.test.TargetBackend @@ -30,14 +31,23 @@ import java.io.File private const val MODULE_EMULATION_FILE = "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/moduleEmulation.js" -val File.esModulesSubDir: File - get() = File(absolutePath + "_esm") +fun TestModule.getNameFor(filePath: String, testServices: TestServices): String { + return JsEnvironmentConfigurator.getJsArtifactSimpleName(testServices, name) + "-js-" + filePath +} -private fun extractJsFiles(testServices: TestServices, modules: List): Pair, List> { - val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices) +fun TestModule.getNameFor(file: TestFile, testServices: TestServices): String { + return getNameFor(file.name, testServices) +} + +private fun extractJsFiles( + testServices: TestServices, + modules: List, + mode: TranslationMode = TranslationMode.FULL, +): Pair, List> { + val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, mode) fun copyInputJsFile(module: TestModule, inputJsFile: TestFile): String { - val newName = JsEnvironmentConfigurator.getJsArtifactSimpleName(testServices, module.name) + "-js-" + inputJsFile.name + val newName = module.getNameFor(inputJsFile, testServices) val targetFile = File(outputDir, newName) targetFile.writeText(inputJsFile.originalContent) return targetFile.absolutePath @@ -45,49 +55,74 @@ private fun extractJsFiles(testServices: TestServices, modules: List val inputJsFiles = modules .flatMap { module -> module.files.map { module to it } } - .filter { it.second.isJsFile } - + .filter { it.second.isJsFile || it.second.isMjsFile } val after = inputJsFiles - .filter { (_, inputJsFile) -> inputJsFile.name.endsWith("__after.js") } + .filter { (module, inputJsFile) -> inputJsFile.name.endsWith("__after${module.kind.extension}") } .map { (module, inputJsFile) -> copyInputJsFile(module, inputJsFile) } val before = inputJsFiles - .filterNot { (_, inputJsFile) -> inputJsFile.name.endsWith("__after.js") } + .filterNot { (module, inputJsFile) -> inputJsFile.name.endsWith("__after${module.kind.extension}") } .map { (module, inputJsFile) -> copyInputJsFile(module, inputJsFile) } return before to after } -private fun getAdditionalFiles(testServices: TestServices): List { +fun getAdditionalFilePathes(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL): List { + return getAdditionalFiles(testServices, mode, true).map { it.absolutePath } +} + +fun getAdditionalFiles( + testServices: TestServices, + mode: TranslationMode = TranslationMode.FULL, + shouldCopyFiles: Boolean = false +): List { val originalFile = testServices.moduleStructure.originalTestDataFiles.first() val withModuleSystem = testWithModuleSystem(testServices) - val additionalFiles = mutableListOf() - if (withModuleSystem) additionalFiles += File(MODULE_EMULATION_FILE).absolutePath + val additionalFiles = mutableListOf() + if (withModuleSystem) additionalFiles += File(MODULE_EMULATION_FILE) originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_EXTENSION) .takeIf { it.exists() } - ?.let { additionalFiles += it.absolutePath } + ?.let { additionalFiles += it } + + originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_MODULE_EXTENSION) + .takeIf { it.exists() } + ?.let { + File(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, mode), it.name).apply { + if (shouldCopyFiles) it.copyTo(this, true) + } + } + ?.let { additionalFiles += it } return additionalFiles } -private fun getAdditionalMjsFiles(testServices: TestServices): List { - val originalFile = testServices.moduleStructure.originalTestDataFiles.first() - - return originalFile.parentFile.resolve(originalFile.nameWithoutExtension + ".mjs") - .takeIf { it.exists() } - ?.let { listOf(it.absolutePath) } ?: emptyList() +fun getAdditionalMainFilePathes(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL): List { + return getAdditionalMainFiles(testServices, mode, shouldCopyFiles = true).map { it.absolutePath } } -private fun getAdditionalMainFiles(testServices: TestServices): List { +fun getAdditionalMainFiles( + testServices: TestServices, + mode: TranslationMode = TranslationMode.FULL, + shouldCopyFiles: Boolean = false +): List { val originalFile = testServices.moduleStructure.originalTestDataFiles.first() - val additionalFiles = mutableListOf() + val additionalFiles = mutableListOf() originalFile.parentFile.resolve(originalFile.nameWithoutExtension + "__main.js") .takeIf { it.exists() } - ?.let { additionalFiles += it.absolutePath } + ?.let { additionalFiles += it } + + originalFile.parentFile.resolve(originalFile.nameWithoutExtension + "__main.mjs") + .takeIf { it.exists() } + ?.let { + File(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, mode), it.name).apply { + if (shouldCopyFiles) it.copyTo(this, true) + } + } + ?.let { additionalFiles += it } return additionalFiles } @@ -99,26 +134,31 @@ fun testWithModuleSystem(testServices: TestServices): Boolean { return mainModuleKind != ModuleKind.PLAIN && NO_JS_MODULE_SYSTEM !in globalDirectives } +fun getModeOutputFilePath(testServices: TestServices, module: TestModule, mode: TranslationMode): String { + return JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, mode) + module.kind.extension +} + fun getAllFilesForRunner( testServices: TestServices, modulesToArtifact: Map ): Map> { val originalFile = testServices.moduleStructure.originalTestDataFiles.first() val commonFiles = JsAdditionalSourceProvider.getAdditionalJsFiles(originalFile.parent).map { it.absolutePath } - val (inputJsFilesBefore, inputJsFilesAfter) = extractJsFiles(testServices, testServices.moduleStructure.modules) - val additionalFiles = getAdditionalFiles(testServices) - val additionalMainFiles = getAdditionalMainFiles(testServices) if (modulesToArtifact.values.any { it is BinaryArtifacts.Js.JsIrArtifact }) { // JS IR - val (module, compilerResult) = modulesToArtifact.entries.mapNotNull { (m, c) -> (c as? BinaryArtifacts.Js.JsIrArtifact)?.let { m to c.compilerResult } }.single() - + val (module, compilerResult) = modulesToArtifact.entries.mapNotNull { (m, c) -> (c as? BinaryArtifacts.Js.JsIrArtifact)?.let { m to c.compilerResult } } + .single() val result = mutableMapOf>() compilerResult.outputs.entries.forEach { (mode, outputs) -> val paths = mutableListOf() - val outputFile = JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, mode) + ".js" + val outputFile = getModeOutputFilePath(testServices, module, mode) + val (inputJsFilesBefore, inputJsFilesAfter) = extractJsFiles(testServices, testServices.moduleStructure.modules, mode) + val additionalFiles = getAdditionalFilePathes(testServices, mode) + val additionalMainFiles = getAdditionalMainFilePathes(testServices, mode) + outputs.dependencies.forEach { (moduleId, _) -> paths += outputFile.augmentWithModuleName(moduleId) } @@ -129,12 +169,15 @@ fun getAllFilesForRunner( return result } else { - // Old BE and ES modules + val (inputJsFilesBefore, inputJsFilesAfter) = extractJsFiles(testServices, testServices.moduleStructure.modules) + val additionalFiles = getAdditionalFilePathes(testServices) + val additionalMainFiles = getAdditionalMainFilePathes(testServices) + // Old BE val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices) val dceOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_DCE_MINIMIZED_NAMES) val artifactsPaths = modulesToArtifact.values.map { it.outputFile.absolutePath }.filter { !File(it).isDirectory } - val allJsFiles = additionalFiles + inputJsFilesBefore +artifactsPaths + commonFiles + additionalMainFiles + inputJsFilesAfter + val allJsFiles = additionalFiles + inputJsFilesBefore + artifactsPaths + commonFiles + additionalMainFiles + inputJsFilesAfter val result = mutableMapOf>() @@ -154,38 +197,6 @@ fun getAllFilesForRunner( } } -fun extractAllFilesForEsRunner(testServices: TestServices, esmOutputDir: File): Pair, List> { - val modules = testServices.moduleStructure.modules - val originalFile = testServices.moduleStructure.originalTestDataFiles.first() - - val commonFiles = JsAdditionalSourceProvider.getAdditionalJsFiles(originalFile.parent).map { it.absolutePath } - val (inputJsFilesBefore, inputJsFilesAfter) = extractJsFiles(testServices, modules) - val additionalFiles = getAdditionalFiles(testServices) - val additionalMjsFiles = getAdditionalMjsFiles(testServices) - val additionalMainFiles = getAdditionalMainFiles(testServices) - - val allNonEsModuleFiles = additionalFiles + inputJsFilesBefore + commonFiles - - // Copy __main file if present - if (additionalMainFiles.isNotEmpty()) { - val newFileName = File(esmOutputDir, "test.mjs") - newFileName.delete() - File(additionalMainFiles.first()).copyTo(newFileName) - } - - // Copy all .mjs files into generated directory - modules.flatMap { it.files } - .filter { it.isMjsFile } - .map { File(esmOutputDir, it.name).writeText(it.originalContent) } - - additionalMjsFiles.forEach { mjsFile -> - val outFile = File(esmOutputDir, File(mjsFile).name) - File(mjsFile).copyTo(outFile, overwrite = true) - } - - return Pair(allNonEsModuleFiles, inputJsFilesAfter) -} - fun getOnlyJsFilesForRunner(testServices: TestServices, modulesToArtifact: Map): List { return getAllFilesForRunner(testServices, modulesToArtifact).let { it[TranslationMode.FULL] ?: it[TranslationMode.PER_MODULE]!! @@ -215,8 +226,49 @@ fun getBoxFunction(testServices: TestServices): KtNamedFunction? { }.singleOrNull() } -fun extractTestPackage(testServices: TestServices): String? = - getBoxFunction(testServices)?.containingKtFile?.packageFqName?.asString()?.takeIf { it.isNotEmpty() } +fun extractTestPackage(testServices: TestServices, ignoreEsModules: Boolean = true): String? { + val runPlainBoxFunction = RUN_PLAIN_BOX_FUNCTION in testServices.moduleStructure.allDirectives + if (runPlainBoxFunction) return null + + val ktFiles = testServices.moduleStructure.modules.flatMap { module -> + module.files + .filter { it.isKtFile } + .map { + val project = testServices.compilerConfigurationProvider.getProject(module) + module to testServices.sourceFileProvider.getKtFileForSourceFile(it, project) + } + } + + val fileWithBoxFunction = ktFiles.find { (module, ktFile) -> + (!ignoreEsModules || module.kind != ModuleKind.ES) && + ktFile.declarations.find { it is KtNamedFunction && it.name == TEST_FUNCTION } != null + } ?: return null + + return fileWithBoxFunction.second.packageFqName.asString().takeIf { it.isNotEmpty() } +} + +fun extractEntryModulePath( + mode: TranslationMode, + testServices: TestServices, +): String? = + if (getBoxFunction(testServices) == null) { + testServices.moduleStructure.modules + .find { JsEnvironmentConfigurator.isMainModule(it, testServices) } + ?.run { + files + .find { it.isMjsFile && JsEnvironmentConfigurationDirectives.ENTRY_ES_MODULE in it.directives } + ?.let { + JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, mode).absolutePath + + File.separator + getNameFor(it, testServices) + } + } + + } else { + testServices.moduleStructure.modules + .find { JsEnvironmentConfigurator.isMainModule(it, testServices) } + ?.let { getModeOutputFilePath(testServices, it, mode) } + } + fun getTestChecker(testServices: TestServices): AbstractJsTestChecker { val runTestInNashorn = java.lang.Boolean.getBoolean("kotlin.js.useNashorn") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/JsTestChecker.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/JsTestChecker.kt index bc956876efe..94e0b67bbd3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/JsTestChecker.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/JsTestChecker.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.js.engine.loadFiles import org.junit.Assert private const val DIST_DIR_JS_PATH = "dist/js/" +private const val ESM_EXTENSION = ".mjs" fun createScriptEngine(): ScriptEngine { return if (java.lang.Boolean.getBoolean("kotlin.js.useNashorn")) ScriptEngineNashorn() else ScriptEngineV8() @@ -22,14 +23,26 @@ fun ScriptEngine.overrideAsserter() { eval("this['kotlin-test'].kotlin.test.overrideAsserter_wbnzx$(this['kotlin-test'].kotlin.test.DefaultAsserter);") } +private fun String.escapePath(): String { + return replace("\\", "/") +} + +@Suppress("UNUSED_PARAMETER") fun ScriptEngine.runTestFunction( testModuleName: String?, testPackageName: String?, testFunctionName: String, withModuleSystem: Boolean, testFunctionArgs: String = "", + entryModulePath: String? = null, ): String { + if (withModuleSystem && testModuleName == null && entryModulePath == null) { + error("Entry point was not found. Please specify ENTRY_ES_MODULE directive near js file, if this is ES Modules test.") + } var script = when { + entryModulePath != null && entryModulePath.endsWith(ESM_EXTENSION) -> "globalThis".also { + eval("import('${entryModulePath.escapePath()}').then(module => Object.assign(globalThis, module)).catch(console.error)") + } withModuleSystem -> "\$kotlin_test_internal\$.require('" + testModuleName!! + "')" testModuleName === null -> "this" else -> testModuleName @@ -51,9 +64,10 @@ abstract class AbstractJsTestChecker { testPackageName: String?, testFunctionName: String, expectedResult: String, - withModuleSystem: Boolean + withModuleSystem: Boolean, + entryModulePath: String? = null, ) { - val actualResult = run(files, testModuleName, testPackageName, testFunctionName, "", withModuleSystem) + val actualResult = run(files, testModuleName, testPackageName, testFunctionName, "", withModuleSystem, entryModulePath) Assert.assertEquals(expectedResult, actualResult.normalize()) } @@ -76,9 +90,10 @@ abstract class AbstractJsTestChecker { testPackageName: String?, testFunctionName: String, testFunctionArgs: String, - withModuleSystem: Boolean + withModuleSystem: Boolean, + entryModulePath: String? = null, ) = run(files) { - runTestFunction(testModuleName, testPackageName, testFunctionName, withModuleSystem, testFunctionArgs) + runTestFunction(testModuleName, testPackageName, testFunctionName, withModuleSystem, testFunctionArgs, entryModulePath) } diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index 1910a34d5db..3859bbe7139 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -1906,18 +1906,6 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { public void testAllFilesPresentInExport() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); } - - @Test - @TestMetadata("overriddenExternalMethodWithSameStableNameMethod.kt") - public void testOverriddenExternalMethodWithSameStableNameMethod() throws Exception { - runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt"); - } - - @Test - @TestMetadata("reservedModuleName.kt") - public void testReservedModuleName() throws Exception { - runTest("js/js.translator/testData/box/esModules/export/reservedModuleName.kt"); - } } @Nested diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java index 988b5cba2ad..a9da7ca1d48 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java @@ -2217,35 +2217,173 @@ public class FirJsTestGenerated extends AbstractFirJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @Test + @TestMetadata("bridgeSavingAfterExport.kt") + public void testBridgeSavingAfterExport() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/bridgeSavingAfterExport.kt"); + } + + @Test + @TestMetadata("bridgeSavingAfterExportInExportedFile.kt") + public void testBridgeSavingAfterExportInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/bridgeSavingAfterExportInExportedFile.kt"); + } + + @Test + @TestMetadata("defaultInlineClassConstructorParam.kt") + public void testDefaultInlineClassConstructorParam() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParam.kt"); + } + + @Test + @TestMetadata("defaultInlineClassConstructorParamInExportedFile.kt") + public void testDefaultInlineClassConstructorParamInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParamInExportedFile.kt"); + } + @Test @TestMetadata("exportAllFile.kt") public void testExportAllFile() throws Exception { runTest("js/js.translator/testData/box/esModules/export/exportAllFile.kt"); } + @Test + @TestMetadata("exportEnumClass.kt") + public void testExportEnumClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportEnumClass.kt"); + } + + @Test + @TestMetadata("exportFileWithEnumClass.kt") + public void testExportFileWithEnumClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithEnumClass.kt"); + } + + @Test + @TestMetadata("exportFileWithInterface.kt") + public void testExportFileWithInterface() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithInterface.kt"); + } + + @Test + @TestMetadata("exportFileWithNestedClass.kt") + public void testExportFileWithNestedClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithNestedClass.kt"); + } + + @Test + @TestMetadata("exportFileWithNestedObject.kt") + public void testExportFileWithNestedObject() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithNestedObject.kt"); + } + + @Test + @TestMetadata("exportFileWithProtectedMembers.kt") + public void testExportFileWithProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithProtectedMembers.kt"); + } + + @Test + @TestMetadata("exportFileWithTopLevelProperty.kt") + public void testExportFileWithTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithTopLevelProperty.kt"); + } + + @Test + @TestMetadata("exportInnerClass.kt") + public void testExportInnerClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportInnerClass.kt"); + } + + @Test + @TestMetadata("exportInterface.kt") + public void testExportInterface() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportInterface.kt"); + } + + @Test + @TestMetadata("exportNestedClass.kt") + public void testExportNestedClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportNestedClass.kt"); + } + + @Test + @TestMetadata("exportNestedObject.kt") + public void testExportNestedObject() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportNestedObject.kt"); + } + + @Test + @TestMetadata("exportProtectedMembers.kt") + public void testExportProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportProtectedMembers.kt"); + } + + @Test + @TestMetadata("exportTopLevelProperty.kt") + public void testExportTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportTopLevelProperty.kt"); + } + @Test @TestMetadata("nonIndetifierModuleName.kt") public void testNonIndetifierModuleName() throws Exception { runTest("js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt"); } + @Test + @TestMetadata("nonIndetifierModuleNameInExportedFile.kt") + public void testNonIndetifierModuleNameInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/nonIndetifierModuleNameInExportedFile.kt"); + } + @Test @TestMetadata("overriddenChainNonExportIntermediate.kt") public void testOverriddenChainNonExportIntermediate() throws Exception { runTest("js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt"); } + @Test + @TestMetadata("overriddenChainNonExportIntermediateInExportedFile.kt") + public void testOverriddenChainNonExportIntermediateInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediateInExportedFile.kt"); + } + @Test @TestMetadata("overriddenExternalMethodWithSameNameMethod.kt") public void testOverriddenExternalMethodWithSameNameMethod() throws Exception { runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt"); } + @Test + @TestMetadata("overriddenExternalMethodWithSameStableNameMethod.kt") + public void testOverriddenExternalMethodWithSameStableNameMethod() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt"); + } + + @Test + @TestMetadata("overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt") + public void testOverriddenExternalMethodWithSameStableNameMethodInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt"); + } + @Test @TestMetadata("reservedModuleName.kt") public void testReservedModuleName() throws Exception { runTest("js/js.translator/testData/box/esModules/export/reservedModuleName.kt"); } + + @Test + @TestMetadata("reservedModuleNameInExportedFile.kt") + public void testReservedModuleNameInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/reservedModuleNameInExportedFile.kt"); + } + + @Test + @TestMetadata("vararg.kt") + public void testVararg() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/vararg.kt"); + } } @Nested diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 1a719b10bf9..4ba2919d917 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -2217,35 +2217,173 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/export"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @Test + @TestMetadata("bridgeSavingAfterExport.kt") + public void testBridgeSavingAfterExport() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/bridgeSavingAfterExport.kt"); + } + + @Test + @TestMetadata("bridgeSavingAfterExportInExportedFile.kt") + public void testBridgeSavingAfterExportInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/bridgeSavingAfterExportInExportedFile.kt"); + } + + @Test + @TestMetadata("defaultInlineClassConstructorParam.kt") + public void testDefaultInlineClassConstructorParam() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParam.kt"); + } + + @Test + @TestMetadata("defaultInlineClassConstructorParamInExportedFile.kt") + public void testDefaultInlineClassConstructorParamInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParamInExportedFile.kt"); + } + @Test @TestMetadata("exportAllFile.kt") public void testExportAllFile() throws Exception { runTest("js/js.translator/testData/box/esModules/export/exportAllFile.kt"); } + @Test + @TestMetadata("exportEnumClass.kt") + public void testExportEnumClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportEnumClass.kt"); + } + + @Test + @TestMetadata("exportFileWithEnumClass.kt") + public void testExportFileWithEnumClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithEnumClass.kt"); + } + + @Test + @TestMetadata("exportFileWithInterface.kt") + public void testExportFileWithInterface() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithInterface.kt"); + } + + @Test + @TestMetadata("exportFileWithNestedClass.kt") + public void testExportFileWithNestedClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithNestedClass.kt"); + } + + @Test + @TestMetadata("exportFileWithNestedObject.kt") + public void testExportFileWithNestedObject() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithNestedObject.kt"); + } + + @Test + @TestMetadata("exportFileWithProtectedMembers.kt") + public void testExportFileWithProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithProtectedMembers.kt"); + } + + @Test + @TestMetadata("exportFileWithTopLevelProperty.kt") + public void testExportFileWithTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportFileWithTopLevelProperty.kt"); + } + + @Test + @TestMetadata("exportInnerClass.kt") + public void testExportInnerClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportInnerClass.kt"); + } + + @Test + @TestMetadata("exportInterface.kt") + public void testExportInterface() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportInterface.kt"); + } + + @Test + @TestMetadata("exportNestedClass.kt") + public void testExportNestedClass() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportNestedClass.kt"); + } + + @Test + @TestMetadata("exportNestedObject.kt") + public void testExportNestedObject() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportNestedObject.kt"); + } + + @Test + @TestMetadata("exportProtectedMembers.kt") + public void testExportProtectedMembers() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportProtectedMembers.kt"); + } + + @Test + @TestMetadata("exportTopLevelProperty.kt") + public void testExportTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/exportTopLevelProperty.kt"); + } + @Test @TestMetadata("nonIndetifierModuleName.kt") public void testNonIndetifierModuleName() throws Exception { runTest("js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt"); } + @Test + @TestMetadata("nonIndetifierModuleNameInExportedFile.kt") + public void testNonIndetifierModuleNameInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/nonIndetifierModuleNameInExportedFile.kt"); + } + @Test @TestMetadata("overriddenChainNonExportIntermediate.kt") public void testOverriddenChainNonExportIntermediate() throws Exception { runTest("js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt"); } + @Test + @TestMetadata("overriddenChainNonExportIntermediateInExportedFile.kt") + public void testOverriddenChainNonExportIntermediateInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediateInExportedFile.kt"); + } + @Test @TestMetadata("overriddenExternalMethodWithSameNameMethod.kt") public void testOverriddenExternalMethodWithSameNameMethod() throws Exception { runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt"); } + @Test + @TestMetadata("overriddenExternalMethodWithSameStableNameMethod.kt") + public void testOverriddenExternalMethodWithSameStableNameMethod() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt"); + } + + @Test + @TestMetadata("overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt") + public void testOverriddenExternalMethodWithSameStableNameMethodInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt"); + } + @Test @TestMetadata("reservedModuleName.kt") public void testReservedModuleName() throws Exception { runTest("js/js.translator/testData/box/esModules/export/reservedModuleName.kt"); } + + @Test + @TestMetadata("reservedModuleNameInExportedFile.kt") + public void testReservedModuleNameInExportedFile() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/reservedModuleNameInExportedFile.kt"); + } + + @Test + @TestMetadata("vararg.kt") + public void testVararg() throws Exception { + runTest("js/js.translator/testData/box/esModules/export/vararg.kt"); + } } @Nested diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java index a2465bd44eb..e2729a6869b 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsTypeScriptExportTestGenerated.java @@ -456,6 +456,12 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp runTest("js/js.translator/testData/typescript-export/module-systems/commonjs.kt"); } + @Test + @TestMetadata("esm.kt") + public void testEsm() throws Exception { + runTest("js/js.translator/testData/typescript-export/module-systems/esm.kt"); + } + @Test @TestMetadata("plain.kt") public void testPlain() throws Exception { @@ -484,6 +490,12 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp runTest("js/js.translator/testData/typescript-export/module-systems-in-exported-file/commonjs.kt"); } + @Test + @TestMetadata("esm.kt") + public void testEsm() throws Exception { + runTest("js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.kt"); + } + @Test @TestMetadata("plain.kt") public void testPlain() throws Exception { diff --git a/js/js.translator/testData/box/esModules/export/bridgeSavingAfterExport.kt b/js/js.translator/testData/box/esModules/export/bridgeSavingAfterExport.kt new file mode 100644 index 00000000000..50add1d6875 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/bridgeSavingAfterExport.kt @@ -0,0 +1,31 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: bridge_saving_after_export +// FILE: lib.kt + +@JsExport +open class A { + open fun foo(value: T): T = value +} + +@JsExport +class B: A() { + override fun foo(value: String): String = value +} + +// FILE: entry.mjs +// ENTRY_ES_MODULE +import { A, B } from "./bridgeSavingAfterExport-bridge_saving_after_export_v5.mjs"; + +export function box() { + var a = new A() + var aFoo = a.foo("ok") + if (aFoo != "ok") return "fail 1" + + var b = new B() + var bFoo = b.foo("ok") + if (bFoo != "ok") return "fail 2" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/bridgeSavingAfterExportInExportedFile.kt b/js/js.translator/testData/box/esModules/export/bridgeSavingAfterExportInExportedFile.kt new file mode 100644 index 00000000000..5d42290fd07 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/bridgeSavingAfterExportInExportedFile.kt @@ -0,0 +1,30 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: bridge_saving_after_export +// FILE: lib.kt +@file:JsExport + +open class A { + open fun foo(value: T): T = value +} + +class B: A() { + override fun foo(value: String): String = value +} + +// FILE: entry.mjs +// ENTRY_ES_MODULE +import { A, B } from "./bridgeSavingAfterExportInExportedFile-bridge_saving_after_export_v5.mjs"; + +export function box() { + var a = new A() + var aFoo = a.foo("ok") + if (aFoo != "ok") return "fail 1" + + var b = new B() + var bFoo = b.foo("ok") + if (bFoo != "ok") return "fail 2" + + return "OK" +} diff --git a/js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParam.kt b/js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParam.kt new file mode 100644 index 00000000000..1e2a1552684 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParam.kt @@ -0,0 +1,22 @@ +// IGNORE_FIR +// KT-49225 +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES +// SPLIT_PER_MODULE + +// MODULE: lib +// FILE: lib.kt +value class Koo(val koo: String = "OK") + +@JsExport +class Bar(val koo: Koo = Koo()) + +// MODULE: main(lib) +// FILE: entry.mjs +// ENTRY_ES_MODULE + +import { Bar } from "./defaultInlineClassConstructorParam-kotlin_lib_v5.mjs"; + +export function box() { + return new Bar().koo; +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParamInExportedFile.kt b/js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParamInExportedFile.kt new file mode 100644 index 00000000000..a9c7aeb350a --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/defaultInlineClassConstructorParamInExportedFile.kt @@ -0,0 +1,24 @@ +// IGNORE_FIR +// KT-49225 +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS +// SPLIT_PER_MODULE + +// MODULE: lib +// FILE: koo.kt +value class Koo(val koo: String = "OK") + +// FILE: bar.kt +@file:JsExport + +class Bar(val koo: Koo = Koo()) + +// MODULE: main(lib) +// FILE: entry.mjs +// ENTRY_ES_MODULE + +import { Bar } from "./defaultInlineClassConstructorParamInExportedFile-kotlin_lib_v5.mjs"; + +export function box() { + return new Bar().koo; +} diff --git a/js/js.translator/testData/box/esModules/export/exportAllFile.kt b/js/js.translator/testData/box/esModules/export/exportAllFile.kt index 73b5fe47daa..0fdaab6ebb2 100644 --- a/js/js.translator/testData/box/esModules/export/exportAllFile.kt +++ b/js/js.translator/testData/box/esModules/export/exportAllFile.kt @@ -1,6 +1,5 @@ // DONT_TARGET_EXACT_BACKEND: JS // EXPECTED_REACHABLE_NODES: 1252 -// INFER_MAIN_MODULE // ES_MODULES // MODULE: export_all_file @@ -19,5 +18,9 @@ class B : A() { // FILE: entry.mjs // ENTRY_ES_MODULE -import { B } from "./export_all_file/index.js"; -console.assert(new B().foo("K") == "OK"); \ No newline at end of file + +import { B } from "./exportAllFile-export_all_file_v5.mjs"; + +export function box() { + return new B().foo("K") +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportEnumClass.kt b/js/js.translator/testData/box/esModules/export/exportEnumClass.kt new file mode 100644 index 00000000000..ede64461b34 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportEnumClass.kt @@ -0,0 +1,103 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: export_enum_class +// FILE: lib.kt + +@JsExport +enum class Foo(val constructorParameter: String) { + A("aConstructorParameter"), + B("bConstructorParameter"); + + val foo = ordinal + + fun bar(value: String) = value + + fun bay() = name + + companion object { + val baz = "baz" + } +} + +@JsExport +enum class Bar { + A, + B { + var d = "d" + init { + d = "d2" + } + fun huh() = "huh" + }; + + val foo = ordinal + + fun bar(value: String) = value + + fun bay() = name +} + +@JsExport +class OuterClass { + enum class NestedEnum { + A, + B; + } +} + + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Foo, Bar, OuterClass } from "./exportEnumClass-export_enum_class_v5.mjs" + +export function box() { + if (Foo.A !== Foo.A) return "fail1" + if (Foo.B !== Foo.B) return "fail2" + + if (Foo.Companion.baz !== "baz") return "fail3" + + if (Foo.A.foo !== 0) return "fail4" + if (Foo.B.foo !== 1) return "fail5" + + if (Foo.A.bar("A") !== "A") return "fail6" + if (Foo.B.bar("B") !== "B") return "fail7" + + if (Foo.A.bay() !== "A") return "fail8" + if (Foo.B.bay() !== "B") return "fail9" + + if (Foo.A.constructorParameter !== "aConstructorParameter") return "fail10" + if (Foo.B.constructorParameter !== "bConstructorParameter") return "fail11" + + if (Bar.A.foo !== 0) return "fail12" + if (Bar.B.foo !== 1) return "fail13" + + if (Bar.A.bar("A") !== "A") return "fail14" + if (Bar.B.bar("B") !== "B") return "fail15" + + if (Bar.A.bay() !== "A") return "fail15" + if (Bar.B.bay() !== "B") return "fail16" + + if (Bar.B.constructor.prototype.hasOwnProperty('d')) return "fail17" + if (Bar.B.constructor.prototype.hasOwnProperty('huh')) return "fail18" + + if (Foo.valueOf("A") !== Foo.A) return "fail19" + if (Foo.valueOf("B") !== Foo.B) return "fail20" + + if (Foo.values().indexOf(Foo.A) === -1) return "fail21" + if (Foo.values().indexOf(Foo.B) === -1) return "fail22" + + if (Foo.A.name !== "A") return "fail23" + if (Foo.B.name !== "B") return "fail24" + + if (Foo.A.ordinal !== 0) return "fail25" + if (Foo.B.ordinal !== 1) return "fail26" + + if (OuterClass.NestedEnum.A.name !== "A") return "fail27" + if (OuterClass.NestedEnum.B.name !== "B") return "fail28" + + if (OuterClass.NestedEnum.A.ordinal !== 0) return "fail29" + if (OuterClass.NestedEnum.B.ordinal !== 1) return "fail30" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportFileWithEnumClass.kt b/js/js.translator/testData/box/esModules/export/exportFileWithEnumClass.kt new file mode 100644 index 00000000000..4995b85dbbc --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportFileWithEnumClass.kt @@ -0,0 +1,100 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: export_enum_class +// FILE: lib.kt +@file:JsExport + +enum class Foo(val constructorParameter: String) { + A("aConstructorParameter"), + B("bConstructorParameter"); + + val foo = ordinal + + fun bar(value: String) = value + + fun bay() = name + + companion object { + val baz = "baz" + } +} + +enum class Bar { + A, + B { + var d = "d" + init { + d = "d2" + } + fun huh() = "huh" + }; + + val foo = ordinal + + fun bar(value: String) = value + + fun bay() = name +} + +class OuterClass { + enum class NestedEnum { + A, + B; + } +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Foo, Bar, OuterClass } from "./exportFileWithEnumClass-export_enum_class_v5.mjs" + +export function box() { + if (Foo.A !== Foo.A) return "fail1" + if (Foo.B !== Foo.B) return "fail2" + + if (Foo.Companion.baz !== "baz") return "fail3" + + if (Foo.A.foo !== 0) return "fail4" + if (Foo.B.foo !== 1) return "fail5" + + if (Foo.A.bar("A") !== "A") return "fail6" + if (Foo.B.bar("B") !== "B") return "fail7" + + if (Foo.A.bay() !== "A") return "fail8" + if (Foo.B.bay() !== "B") return "fail9" + + if (Foo.A.constructorParameter !== "aConstructorParameter") return "fail10" + if (Foo.B.constructorParameter !== "bConstructorParameter") return "fail11" + + if (Bar.A.foo !== 0) return "fail12" + if (Bar.B.foo !== 1) return "fail13" + + if (Bar.A.bar("A") !== "A") return "fail14" + if (Bar.B.bar("B") !== "B") return "fail15" + + if (Bar.A.bay() !== "A") return "fail15" + if (Bar.B.bay() !== "B") return "fail16" + + if (Bar.B.constructor.prototype.hasOwnProperty('d')) return "fail17" + if (Bar.B.constructor.prototype.hasOwnProperty('huh')) return "fail18" + + if (Foo.valueOf("A") !== Foo.A) return "fail19" + if (Foo.valueOf("B") !== Foo.B) return "fail20" + + if (Foo.values().indexOf(Foo.A) === -1) return "fail21" + if (Foo.values().indexOf(Foo.B) === -1) return "fail22" + + if (Foo.A.name !== "A") return "fail23" + if (Foo.B.name !== "B") return "fail24" + + if (Foo.A.ordinal !== 0) return "fail25" + if (Foo.B.ordinal !== 1) return "fail26" + + if (OuterClass.NestedEnum.A.name !== "A") return "fail27" + if (OuterClass.NestedEnum.B.name !== "B") return "fail28" + + if (OuterClass.NestedEnum.A.ordinal !== 0) return "fail29" + if (OuterClass.NestedEnum.B.ordinal !== 1) return "fail30" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportFileWithInterface.kt b/js/js.translator/testData/box/esModules/export/exportFileWithInterface.kt new file mode 100644 index 00000000000..4f10876d508 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportFileWithInterface.kt @@ -0,0 +1,99 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: export_interface +// FILE: not_exported.kt + +interface ParentI { + val str: String +} + +interface ExtendedI: I { + fun bar(): Int +} + +open class NotExportedClass(override var value: Int) : ExtendedI { + override var variable: Int = value + override open fun foo(): String = "Not Exported" + override val str: String = "test 1" + override open fun bar(): Int = 42 +} + + +// FILE: exportes.kt +@file:JsExport + +interface I : ParentI { + val value: Int + var variable: Int + fun foo(): String +} + +class ExportedClass(override val value: Int) : ExtendedI { + override var variable: Int = value + override fun foo(): String = "Exported" + override val str: String = "test 2" + override open fun bar(): Int = 43 +} + +class AnotherOne : NotExportedClass(42) { + override fun foo(): String = "Another One Exported" +} + +fun generateNotExported(value: Int): NotExportedClass { + return NotExportedClass(value) +} + +fun consume(i: I): String { + return "Value is ${i.value}, variable is ${i.variable} and result is '${i.foo()}'" +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import * as pkg from "./exportFileWithInterface-export_interface_v5.mjs" + +export function box() { + const { I, ExportedClass, AnotherOne, generateNotExported, consume } = pkg + if (I !== undefined) return "Fail: module should not export interface in runtime" + + const exported = new ExportedClass(1) + const another = new AnotherOne() + const notExported = generateNotExported (3) + + if (exported.foo() !== "Exported") return "Fail: foo function was not generated for ExportedClass" + if (another.foo() !== "Another One Exported") return "Fail: foo function was not generated for AnotherOne" + if (notExported.foo() !== "Not Exported") return "Fail: foo function was not generated for NotExportedClass" + + if (exported.value !== 1) return "Fail: value getter was not generated for ExportedClass" + if (another.value !== 42) return "Fail: value getter was not generated for AnotherOne" + if (notExported.value !== 3) return "Fail: value getter was not generated for NotExportedClass" + + if (exported.variable !== 1) return "Fail: variable getter was not generated for ExportedClass" + if (another.variable !== 42) return "Fail: variable getter was not generated for AnotherOne" + if (notExported.variable !== 3) return "Fail: variable getter was not generated for NotExportedClass" + + exported.variable = 101 + another.variable = 102 + notExported.variable = 103 + + if (exported.variable !== 101) return "Fail: variable setter was not generated for ExportedClass" + if (another.variable !== 102) return "Fail: variable setter was not generated for AnotherOne" + if (notExported.variable !== 103) return "Fail: variable setter was not generated for NotExportedClass" + + try { + notExported.value = 42 + } catch(e) {} + if (notExported.value !== 3) return "Fail: value setter was generated for NotExportedClass, but it shouldn't" + + if (consume(exported) !== "Value is 1, variable is 101 and result is 'Exported'") return "Fail: methods or fields of ExportedClass was mangled" + if (consume(another) !== "Value is 42, variable is 102 and result is 'Another One Exported'") return "Fail: methods or fields of AnotherOne was mangled" + if (consume(notExported) !== "Value is 3, variable is 103 and result is 'Not Exported'") return "Fail: methods or fields of NotExported was mangled" + + if (notExported.str !== undefined) return "Fail: str should not exist inside NotExportedClass" + if (exported.str !== undefined) return "Fail: str should not exist inside ExportedClass" + + if (notExported.bar !== undefined) return "Fail: bar should not exist inside NotExportedClass" + if (exported.bar !== undefined) return "Fail: bar should not exist inside ExportedClass" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportFileWithNestedClass.kt b/js/js.translator/testData/box/esModules/export/exportFileWithNestedClass.kt new file mode 100644 index 00000000000..8eea8ba01ff --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportFileWithNestedClass.kt @@ -0,0 +1,51 @@ +// EXPECTED_REACHABLE_NODES: 1252 +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES +// SKIP_DCE_DRIVEN + +// MODULE: export_nested_class +// FILE: lib.kt +@file:JsExport + +abstract class A { + abstract fun foo(k: String): String +} + +class B { + class Foo : A() { + override fun foo(k: String): String { + return "O" + k + } + + fun bar(k: String): String { + return foo(k) + } + } +} + +object MyObject { + class A { + fun valueA() = "OK" + } + class B { + fun valueB() = "OK" + } + class C { + fun valueC() = "OK" + } +} + +// FILE: test.mjs +// ENTRY_ES_MODULE +import { B, MyObject } from "./exportFileWithNestedClass-export_nested_class_v5.mjs" + +export function box() { + if (new B.Foo().bar("K") != "OK") return "fail 1"; + + const myObject = MyObject.getInstance() + if (new myObject.A().valueA() != "OK") return "fail 2"; + if (new myObject.B().valueB() != "OK") return "fail 3"; + if (new myObject.C().valueC() != "OK") return "fail 4"; + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportFileWithNestedObject.kt b/js/js.translator/testData/box/esModules/export/exportFileWithNestedObject.kt new file mode 100644 index 00000000000..e8fa88d9e11 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportFileWithNestedObject.kt @@ -0,0 +1,70 @@ +// EXPECTED_REACHABLE_NODES: 1265 +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS +// SKIP_MINIFICATION +// SKIP_DCE_DRIVEN +// SKIP_NODE_JS + +// See KT-43783 + +// MODULE: nestedObjectExport +// FILE: lib.kt +@file:JsExport + +class Abc { + companion object AbcCompanion { + fun xyz(): String = "Companion object method OK" + + val prop: String + get() = "Companion object property OK" + } +} + +class Foo { + companion object { + fun xyz(): String = "Companion object method OK" + + val prop: String + get() = "Companion object property OK" + } +} + +sealed class MyEnum(val name: String) { + object A: MyEnum("A") + object B: MyEnum("B") + object C: MyEnum("C") +} + +object MyObject { + object A { + fun valueA() = "OK" + } + object B { + fun valueB() = "OK" + } + object C { + fun valueC() = "OK" + } +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Foo, Abc, MyEnum, MyObject } from "./exportFileWithNestedObject-nestedObjectExport_v5.mjs" + +export function box() { + if (Abc.AbcCompanion.xyz() != 'Companion object method OK') return 'companion object function failure'; + if (Abc.AbcCompanion.prop != 'Companion object property OK') return 'companion object property failure'; + + if (Foo.Companion.xyz() != 'Companion object method OK') return 'companion object function failure'; + if (Foo.Companion.prop != 'Companion object property OK') return 'companion object property failure'; + + if (MyEnum.A.name != 'A') return 'MyEnum.A failure'; + if (MyEnum.B.name != 'B') return 'MyEnum.B failure'; + if (MyEnum.C.name != 'C') return 'MyEnum.C failure'; + + if (MyObject.getInstance().A.valueA() != "OK") return 'MyObject.A failure'; + if (MyObject.getInstance().B.valueB() != "OK") return 'MyObject.B failure'; + if (MyObject.getInstance().C.valueC() != "OK") return 'MyObject.C failure'; + + return 'OK'; +} diff --git a/js/js.translator/testData/box/esModules/export/exportFileWithProtectedMembers.kt b/js/js.translator/testData/box/esModules/export/exportFileWithProtectedMembers.kt new file mode 100644 index 00000000000..601534d6bad --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportFileWithProtectedMembers.kt @@ -0,0 +1,69 @@ +// EXPECTED_REACHABLE_NODES: 1265 +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS +// SKIP_MINIFICATION +// SKIP_NODE_JS +// SKIP_DCE_DRIVEN + +// MODULE: exportProtectedMembers +// FILE: lib.kt +@file:JsExport + +open class Foo protected constructor() { + protected fun bar(): String = "protected method" + + private var _baz: String = "baz" + + protected var baz: String + get() = _baz + set(value) { + _baz = value + } + + protected val bazReadOnly: String + get() = _baz + + protected val quux: String = "quux" + + protected var quuz: String = "quuz" + + protected class NestedClass { + val prop: String = "nested class property" + } + protected object NestedObject { + val prop: String = "nested object property" + } + + protected companion object { + val prop: String = "companion object property" + } +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Foo } from "./exportFileWithProtectedMembers-exportProtectedMembers_v5.mjs" + +export function box() { + var foo = new Foo(); + + if (foo.bar() != 'protected method') return 'failed to call protected method'; + if (foo.baz != 'baz') return 'failed to read `baz`'; + if (foo.bazReadOnly != 'baz') return 'failed to read `bazReadOnly`'; + foo.baz = 'beer'; + if (foo.baz != 'beer') return 'failed to write protected var'; + if (foo.bazReadOnly != 'beer') return 'unexpected value of `bazReadOnly` after modifying `baz`'; + if (foo.quux != 'quux') return 'failed to read `quux`'; + if (foo.quuz != 'quuz') return 'failed to read `quuz`'; + foo.quuz = 'ale'; + if (foo.quuz != 'ale') return 'failed to write `quuz`'; + + var nestedClass = new Foo.NestedClass() + if (nestedClass.prop != 'nested class property') + return 'failed to read protected class property' + if (Foo.NestedObject.prop != 'nested object property') + return 'failed to read protected nested object property' + if (Foo.Companion.prop != 'companion object property') + return 'failed to read protected companion object property' + + return 'OK'; +} diff --git a/js/js.translator/testData/box/esModules/export/exportFileWithTopLevelProperty.kt b/js/js.translator/testData/box/esModules/export/exportFileWithTopLevelProperty.kt new file mode 100644 index 00000000000..e442bd277db --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportFileWithTopLevelProperty.kt @@ -0,0 +1,58 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// EXPECTED_REACHABLE_NODES: 1252 +// INFER_MAIN_MODULE +// ES_MODULES + +// MODULE: exported_properites +// FILE: lib.kt +@file:JsExport + +val regularValueProperty: String = "regularValueProperty" + +val regularPropertyGetter: String + get() = "regularPropertyGetter" + +var regularVariableProperty: String = "regularVariableProperty" + +var regularVariableGetterWithSetter: String = "regularVariableGetterWithSetter" + get() = "$field by custom getter" + set(value) { field = "$value set by custom setter" } + +// FILE: entry.mjs +// ENTRY_ES_MODULE + +import { + regularValueProperty, + regularPropertyGetter, + regularVariableProperty, + regularVariableGetterWithSetter +} from "./exportFileWithTopLevelProperty-exported_properites_v5.mjs"; + +export function box() { + if (typeof regularValueProperty.get !== "function" || regularValueProperty.get() !== "regularValueProperty") { + return "Fail: wrongly exported getter for regular `val` property" + } + if (typeof regularValueProperty.set !== "undefined") { + return "Fail: wrongly exported setter for regular `val` property" + } + if (typeof regularPropertyGetter.get !== "function" || regularPropertyGetter.get() !== "regularPropertyGetter") { + return "Fail: wrongly exported getter for a `val` property with custom getter" + } + if (typeof regularPropertyGetter.set !== "undefined") { + return "Fail: wrongly exported setter for a `val` property with custom getter" + } + if (typeof regularVariableProperty.get !== "function" || regularVariableProperty.get() !== "regularVariableProperty") { + return "Fail: wrongly exported getter for regular `var` property" + } + if (typeof regularVariableProperty.set !== "function" || (regularVariableProperty.set("test1"), regularVariableProperty.get()) !== "test1") { + return "Fail: wrongly exported setter for regular `var` property" + } + if (typeof regularVariableGetterWithSetter.get !== "function" || regularVariableGetterWithSetter.get() !== "regularVariableGetterWithSetter by custom getter") { + return "Fail: wrongly exported getter for a `var` property with custom getter and setter" + } + if (typeof regularVariableGetterWithSetter.set !== "function" || (regularVariableGetterWithSetter.set("test1"), regularVariableGetterWithSetter.get()) !== "test1 set by custom setter by custom getter") { + return "Fail: wrongly exported setter for a `var` property with custom getter and setter" + } + + return "OK" +} diff --git a/js/js.translator/testData/box/esModules/export/exportInnerClass.kt b/js/js.translator/testData/box/esModules/export/exportInnerClass.kt new file mode 100644 index 00000000000..af90bdfb054 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportInnerClass.kt @@ -0,0 +1,66 @@ +// EXPECTED_REACHABLE_NODES: 1252 +// IGNORE_BACKEND: JS +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS + +// MODULE: export_inner_class +// FILE: lib.kt + +@JsExport +class RegularParent(val value: String) { + inner class RegularInner(val message: String, val anotherValue: Int) { + fun getResult() = value + message + } +} + + +@JsExport +class ParentForSecondary { + inner class InnerWithSecondaryConstructor(val value: String) { + @JsName("innerSuccess") + constructor(): this("OK") + } +} + +@JsExport +class ParentWithSecondary(val value: String) { + @JsName("createO") + constructor(): this("O") + + inner class InnerWithSecondaryConstructor(val anotherValue: String) { + @JsName("createK") + constructor(): this("K") + + fun getResult() = value + anotherValue + } +} + + + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { RegularParent, ParentForSecondary, ParentWithSecondary } from "./exportInnerClass-export_inner_class_v5.mjs" + +export function box() { + var regularParent = new RegularParent("O") + var regularInner = new regularParent.RegularInner("K", 42) + + if (regularInner.anotherValue !== 42) return "Fail: second parameter of the RegularInner primary constructor was ignored" + if (regularInner.getResult() !== "OK") return "Fail: something is going wrong with the outer this capturing logic" + + var parentForSecondary = new ParentForSecondary() + var innerWithSecondary = new parentForSecondary.InnerWithSecondaryConstructor("OK") + + if (innerWithSecondary.value !== "OK") return "Fail: something is going wrong with primary constructor when a secondary one exists" + + var fromSecondary = parentForSecondary.InnerWithSecondaryConstructor.innerSuccess() + + if (fromSecondary.value !== "OK") return "Fail: something is going wrong with secondary constructor inside the inner class" + + var parentFromSecondary = ParentWithSecondary.createO() + var innerFromSecondary = parentFromSecondary.InnerWithSecondaryConstructor.createK() + + if (innerFromSecondary.getResult() !== "OK") return "Fail: there is a problem when both parent and inner class have secondary constructors" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportInterface.kt b/js/js.translator/testData/box/esModules/export/exportInterface.kt new file mode 100644 index 00000000000..a13ad4de850 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportInterface.kt @@ -0,0 +1,105 @@ +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: export_interface +// FILE: lib.kt + +interface ParentI { + val str: String +} + +@JsExport +interface I : ParentI { + val value: Int + var variable: Int + fun foo(): String +} + +interface ExtendedI: I { + fun bar(): Int +} + +open class NotExportedClass(override var value: Int) : ExtendedI { + override var variable: Int = value + override open fun foo(): String = "Not Exported" + override val str: String = "test 1" + override open fun bar(): Int = 42 +} + +@JsExport +class ExportedClass(override val value: Int) : ExtendedI { + override var variable: Int = value + override fun foo(): String = "Exported" + override val str: String = "test 2" + override open fun bar(): Int = 43 +} + +@JsExport +class AnotherOne : NotExportedClass(42) { + override fun foo(): String = "Another One Exported" +} + +@JsExport +fun generateNotExported(value: Int): NotExportedClass { + return NotExportedClass(value) +} + +@JsExport +fun consume(i: I): String { + return "Value is ${i.value}, variable is ${i.variable} and result is '${i.foo()}'" +} + +// FILE: main.mjs +// ENTRY_ES_MODULE + +import * as pkg from "./exportInterface-export_interface_v5.mjs" + +export function box() { + const { I, ExportedClass, AnotherOne, generateNotExported, consume } = pkg + + if (I !== undefined) return "Fail: module should not export interface in runtime" + + const exported = new ExportedClass(1) + const another = new AnotherOne() + const notExported = generateNotExported (3) + + if (exported.foo() !== "Exported") return "Fail: foo function was not generated for ExportedClass" + if (another.foo() !== "Another One Exported") return "Fail: foo function was not generated for AnotherOne" + if (notExported.foo() !== "Not Exported") return "Fail: foo function was not generated for NotExportedClass" + + if (exported.value !== 1) return "Fail: value getter was not generated for ExportedClass" + if (another.value !== 42) return "Fail: value getter was not generated for AnotherOne" + if (notExported.value !== 3) return "Fail: value getter was not generated for NotExportedClass" + + if (exported.variable !== 1) return "Fail: variable getter was not generated for ExportedClass" + if (another.variable !== 42) return "Fail: variable getter was not generated for AnotherOne" + if (notExported.variable !== 3) return "Fail: variable getter was not generated for NotExportedClass" + + exported.variable = 101 + another.variable = 102 + notExported.variable = 103 + + if (exported.variable !== 101) return "Fail: variable setter was not generated for ExportedClass" + if (another.variable !== 102) return "Fail: variable setter was not generated for AnotherOne" + if (notExported.variable !== 103) return "Fail: variable setter was not generated for NotExportedClass" + + try { + notExported.value = 42 + } catch(e) {} + + if (notExported.value !== 3) return "Fail: value setter was generated for NotExportedClass, but it shouldn't" + + if (consume(exported) !== "Value is 1, variable is 101 and result is 'Exported'") return "Fail: methods or fields of ExportedClass was mangled" + if (consume(another) !== "Value is 42, variable is 102 and result is 'Another One Exported'") return "Fail: methods or fields of AnotherOne was mangled" + if (consume(notExported) !== "Value is 3, variable is 103 and result is 'Not Exported'") return "Fail: methods or fields of NotExported was mangled" + + if (notExported.str !== undefined) return "Fail: str should not exist inside NotExportedClass" + if (exported.str !== undefined) return "Fail: str should not exist inside ExportedClass" + + if (notExported.bar !== undefined) return "Fail: bar should not exist inside NotExportedClass" + if (exported.bar !== undefined) return "Fail: bar should not exist inside ExportedClass" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportNestedClass.kt b/js/js.translator/testData/box/esModules/export/exportNestedClass.kt new file mode 100644 index 00000000000..3f733953914 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportNestedClass.kt @@ -0,0 +1,52 @@ +// EXPECTED_REACHABLE_NODES: 1252 +// IGNORE_BACKEND: JS +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS +// SKIP_DCE_DRIVEN + +// MODULE: export_nested_class +// FILE: lib.kt + +abstract class A { + abstract fun foo(k: String): String +} + +@JsExport +class B { + class Foo : A() { + override fun foo(k: String): String { + return "O" + k + } + + fun bar(k: String): String { + return foo(k) + } + } +} + +@JsExport +object MyObject { + class A { + fun valueA() = "OK" + } + class B { + fun valueB() = "OK" + } + class C { + fun valueC() = "OK" + } +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { B, MyObject } from "./exportNestedClass-export_nested_class_v5.mjs" + +export function box() { + if (new B.Foo().bar("K") != "OK") return "fail 1"; + const myObject = MyObject.getInstance() + if (new myObject.A().valueA() != "OK") return "fail 2"; + if (new myObject.B().valueB() != "OK") return "fail 3"; + if (new myObject.C().valueC() != "OK") return "fail 4"; + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/exportNestedObject.kt b/js/js.translator/testData/box/esModules/export/exportNestedObject.kt new file mode 100644 index 00000000000..3431e9f7aa2 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportNestedObject.kt @@ -0,0 +1,73 @@ +// EXPECTED_REACHABLE_NODES: 1265 +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS +// SKIP_MINIFICATION +// SKIP_DCE_DRIVEN +// SKIP_NODE_JS + +// See KT-43783 + +// MODULE: nestedObjectExport +// FILE: lib.kt + +@JsExport +class Abc { + companion object AbcCompanion { + fun xyz(): String = "Companion object method OK" + + val prop: String + get() = "Companion object property OK" + } +} + +@JsExport +class Foo { + companion object { + fun xyz(): String = "Companion object method OK" + + val prop: String + get() = "Companion object property OK" + } +} + +@JsExport +sealed class MyEnum(val name: String) { + object A: MyEnum("A") + object B: MyEnum("B") + object C: MyEnum("C") +} + +@JsExport +object MyObject { + object A { + fun valueA() = "OK" + } + object B { + fun valueB() = "OK" + } + object C { + fun valueC() = "OK" + } +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Abc, Foo, MyEnum, MyObject } from "./exportNestedObject-nestedObjectExport_v5.mjs" + +export function box() { + if (Abc.AbcCompanion.xyz() != 'Companion object method OK') return 'companion object function failure'; + if (Abc.AbcCompanion.prop != 'Companion object property OK') return 'companion object property failure'; + + if (Foo.Companion.xyz() != 'Companion object method OK') return 'companion object function failure'; + if (Foo.Companion.prop != 'Companion object property OK') return 'companion object property failure'; + + if (MyEnum.A.name != 'A') return 'MyEnum.A failure'; + if (MyEnum.B.name != 'B') return 'MyEnum.B failure'; + if (MyEnum.C.name != 'C') return 'MyEnum.C failure'; + + if (MyObject.getInstance().A.valueA() != "OK") return 'MyObject.A failure'; + if (MyObject.getInstance().B.valueB() != "OK") return 'MyObject.B failure'; + if (MyObject.getInstance().C.valueC() != "OK") return 'MyObject.C failure'; + + return 'OK'; +} diff --git a/js/js.translator/testData/box/esModules/export/exportProtectedMembers.kt b/js/js.translator/testData/box/esModules/export/exportProtectedMembers.kt new file mode 100644 index 00000000000..833069494bb --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportProtectedMembers.kt @@ -0,0 +1,69 @@ +// EXPECTED_REACHABLE_NODES: 1265 +// ES_MODULES +// DONT_TARGET_EXACT_BACKEND: JS +// SKIP_MINIFICATION +// SKIP_NODE_JS +// SKIP_DCE_DRIVEN + +// MODULE: exportProtectedMembers +// FILE: lib.kt + +@JsExport +open class Foo protected constructor() { + protected fun bar(): String = "protected method" + + private var _baz: String = "baz" + + protected var baz: String + get() = _baz + set(value) { + _baz = value + } + + protected val bazReadOnly: String + get() = _baz + + protected val quux: String = "quux" + + protected var quuz: String = "quuz" + + protected class NestedClass { + val prop: String = "nested class property" + } + protected object NestedObject { + val prop: String = "nested object property" + } + + protected companion object { + val prop: String = "companion object property" + } +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Foo } from "./exportProtectedMembers-exportProtectedMembers_v5.mjs" + +export function box() { + var foo = new Foo(); + + if (foo.bar() != 'protected method') return 'failed to call protected method'; + if (foo.baz != 'baz') return 'failed to read `baz`'; + if (foo.bazReadOnly != 'baz') return 'failed to read `bazReadOnly`'; + foo.baz = 'beer'; + if (foo.baz != 'beer') return 'failed to write protected var'; + if (foo.bazReadOnly != 'beer') return 'unexpected value of `bazReadOnly` after modifying `baz`'; + if (foo.quux != 'quux') return 'failed to read `quux`'; + if (foo.quuz != 'quuz') return 'failed to read `quuz`'; + foo.quuz = 'ale'; + if (foo.quuz != 'ale') return 'failed to write `quuz`'; + + var nestedClass = new Foo.NestedClass() + if (nestedClass.prop != 'nested class property') + return 'failed to read protected class property' + if (Foo.NestedObject.prop != 'nested object property') + return 'failed to read protected nested object property' + if (Foo.Companion.prop != 'companion object property') + return 'failed to read protected companion object property' + + return 'OK'; +} diff --git a/js/js.translator/testData/box/esModules/export/exportTopLevelProperty.kt b/js/js.translator/testData/box/esModules/export/exportTopLevelProperty.kt new file mode 100644 index 00000000000..5509af5f2ee --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/exportTopLevelProperty.kt @@ -0,0 +1,60 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// EXPECTED_REACHABLE_NODES: 1252 +// ES_MODULES + +// MODULE: exported_properites +// FILE: lib.kt + +@JsExport +val regularValueProperty: String = "regularValueProperty" + +@JsExport +val regularPropertyGetter: String + get() = "regularPropertyGetter" + +@JsExport +var regularVariableProperty: String = "regularVariableProperty" + +@JsExport +var regularVariableGetterWithSetter: String = "regularVariableGetterWithSetter" + get() = "$field by custom getter" + set(value) { field = "$value set by custom setter" } + +// FILE: entry.mjs +// ENTRY_ES_MODULE + +import { + regularValueProperty, + regularPropertyGetter, + regularVariableProperty, + regularVariableGetterWithSetter +} from "./exportTopLevelProperty-exported_properites_v5.mjs"; + +export function box() { + if (typeof regularValueProperty.get !== "function" || regularValueProperty.get() !== "regularValueProperty") { + return "Fail: wrongly exported getter for regular `val` property" + } + if (typeof regularValueProperty.set !== "undefined") { + return "Fail: wrongly exported setter for regular `val` property" + } + if (typeof regularPropertyGetter.get !== "function" || regularPropertyGetter.get() !== "regularPropertyGetter") { + return "Fail: wrongly exported getter for a `val` property with custom getter" + } + if (typeof regularPropertyGetter.set !== "undefined") { + return "Fail: wrongly exported setter for a `val` property with custom getter" + } + if (typeof regularVariableProperty.get !== "function" || regularVariableProperty.get() !== "regularVariableProperty") { + return "Fail: wrongly exported getter for regular `var` property" + } + if (typeof regularVariableProperty.set !== "function" || (regularVariableProperty.set("test1"), regularVariableProperty.get()) !== "test1") { + return "Fail: wrongly exported setter for regular `var` property" + } + if (typeof regularVariableGetterWithSetter.get !== "function" || regularVariableGetterWithSetter.get() !== "regularVariableGetterWithSetter by custom getter") { + return "Fail: wrongly exported getter for a `var` property with custom getter and setter" + } + if (typeof regularVariableGetterWithSetter.set !== "function" || (regularVariableGetterWithSetter.set("test1"), regularVariableGetterWithSetter.get()) !== "test1 set by custom setter by custom getter") { + return "Fail: wrongly exported setter for a `var` property with custom getter and setter" + } + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt b/js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt index 416024a50b0..17248104b1f 100644 --- a/js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt +++ b/js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt @@ -1,10 +1,9 @@ // DONT_TARGET_EXACT_BACKEND: JS // SKIP_MINIFICATION -// INFER_MAIN_MODULE // SKIP_NODE_JS // ES_MODULES -// MODULE: non_identifier_module_name +// MODULE: lib // FILE: lib.kt @JsName("foo") @JsExport @@ -12,5 +11,8 @@ public fun foo(k: String): String = "O$k" // FILE: entry.mjs // ENTRY_ES_MODULE -import { foo } from "./non_identifier_module_name/index.js"; -console.assert(foo("K") == "OK"); +import { foo } from "./nonIndetifierModuleName-lib_v5.mjs"; + +export function box() { + return foo("K") +} diff --git a/js/js.translator/testData/box/esModules/export/nonIndetifierModuleNameInExportedFile.kt b/js/js.translator/testData/box/esModules/export/nonIndetifierModuleNameInExportedFile.kt new file mode 100644 index 00000000000..f972cbd30ea --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/nonIndetifierModuleNameInExportedFile.kt @@ -0,0 +1,21 @@ +// EXPECTED_REACHABLE_NODES: 1270 +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES +// SKIP_MINIFICATION +// SKIP_NODE_JS + +// MODULE: lib +// FILE: lib.kt +@file:JsExport + +@JsName("foo") +public fun foo(k: String): String = "O$k" + +// FILE: enty.mjs +// ENTRY_ES_MODULE + +import { foo } from "./nonIndetifierModuleNameInExportedFile-lib_v5.mjs"; + +export function box() { + return foo("K") +} diff --git a/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt b/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt index 96fac56fc54..7c5e05bac0c 100644 --- a/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt +++ b/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt @@ -1,6 +1,5 @@ // DONT_TARGET_EXACT_BACKEND: JS // EXPECTED_REACHABLE_NODES: 1252 -// INFER_MAIN_MODULE // ES_MODULES // MODULE: overriden_chain_non_export_intermediate @@ -28,12 +27,19 @@ class C : B() { // FILE: entry.mjs // ENTRY_ES_MODULE -import { C } from "./overriden_chain_non_export_intermediate/index.js"; +import { C } from "./overriddenChainNonExportIntermediate-overriden_chain_non_export_intermediate_v5.mjs"; -function test(c) { - if (c.foo() === "foo" && c.bar() === "bar" && c.bay() == "bay") return "OK" +export function box() { + const c = new C() - return "fail" -} + const foo = c.foo() + if (foo !== "foo") return `Fail: expect c.foo() to return 'foo' but it returns ${foo}` -console.assert(test(new C()) == "OK"); \ No newline at end of file + const bar = c.bar() + if (bar !== "bar") return `Fail: expect c.bar() to return 'bar' but it returns ${bar}` + + const bay = c.bay() + if (bay !== "bay") return `Fail: expect c.bay() to return 'bay' but it returns ${bay}` + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediateInExportedFile.kt b/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediateInExportedFile.kt new file mode 100644 index 00000000000..bbc13901c2b --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediateInExportedFile.kt @@ -0,0 +1,39 @@ +// EXPECTED_REACHABLE_NODES: 1252 +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: overriden_chain_non_export_intermediate +// FILE: not_exported.kt +abstract class B : A() { + abstract fun baz(): String + + override fun foo(): String = "foo" +} + + +// FILE: exported.kt +@file:JsExport + +abstract class A { + abstract fun foo(): String + + abstract fun bar(): String +} + +class C : B() { + override fun bar(): String = "bar" + override fun baz(): String = "baz" + + fun bay(): String = "bay" +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { C } from "./overriddenChainNonExportIntermediateInExportedFile-overriden_chain_non_export_intermediate_v5.mjs" + +export function box() { + var c = new C(); + if (c.foo() === "foo" && c.bar() === "bar" && c.bay() == "bay") return "OK" + + return "fail" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt b/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt index e67e7970492..477900f151c 100644 --- a/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt +++ b/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt @@ -1,9 +1,8 @@ // EXPECTED_REACHABLE_NODES: 1252 -// INFER_MAIN_MODULE // DONT_TARGET_EXACT_BACKEND: JS // ES_MODULES -// MODULE: overriden_external_method_with_same_name_method +// MODULE: lib // FILE: lib.kt external abstract class Foo { abstract fun o(): String @@ -32,10 +31,9 @@ Foo.prototype.k = function() { // FILE: entry.mjs // ENTRY_ES_MODULE -import { Baz } from "./overriden_external_method_with_same_name_method/index.js"; +import { Baz } from "./overriddenExternalMethodWithSameNameMethod-lib_v5.mjs"; -function test(foo) { +export function box() { + const foo = new Baz() return foo.o() + foo.k() -} - -console.assert(test(new Baz()) == "OK"); \ No newline at end of file +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt b/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt index f509e032b19..9514188d5be 100644 --- a/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt +++ b/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethod.kt @@ -1,12 +1,8 @@ // EXPECTED_REACHABLE_NODES: 1252 -// IGNORE_BACKEND: JS -// INFER_MAIN_MODULE +// DONT_TARGET_EXACT_BACKEND: JS // ES_MODULES -// TODO: Fix tests on Windows -// DONT_TARGET_EXACT_BACKEND: JS_IR - -// MODULE: overriden_external_method_with_same_stable_name_method +// MODULE: lib // FILE: lib.kt external abstract class Foo { abstract fun o(): String @@ -36,12 +32,11 @@ Foo.prototype.k = function() { // FILE: entry.mjs // ENTRY_ES_MODULE -import { Baz } from "./overriden-external-method-with-same-stable-name-method/index.js"; +import { Baz } from "./overriddenExternalMethodWithSameStableNameMethod-lib_v5.mjs"; -function test(foo) { +export function box() { + const foo = new Baz() const oStable = foo.oStable("OK") if (oStable !== "OK") return "false: " + oStable return foo.o() + foo.k() -} - -console.assert(test(new Baz()) == "OK"); \ No newline at end of file +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt b/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt new file mode 100644 index 00000000000..1ce7d09ef3b --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameStableNameMethodInExportedFile.kt @@ -0,0 +1,49 @@ +// EXPECTED_REACHABLE_NODES: 1252 +// DONT_TARGET_EXACT_BACKEND: JS +// ES_MODULES + +// MODULE: lib +// FILE: not_exported.kt + +external abstract class Foo { + abstract fun o(): String +} + +abstract class Bar : Foo() { + @JsName("oStable") + abstract fun String.o(): String + + override fun o(): String { + return "O".o() + } +} + +// FILE: exported.kt +@file:JsExport + +class Baz : Bar() { + override fun String.o(): String { + return this + } +} + +// FILE: foo.js +function Foo() {} +Foo.prototype.k = function() { + return "K" +} + + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { Baz } from "./overriddenExternalMethodWithSameStableNameMethodInExportedFile-lib_v5.mjs" + +export function box() { + return test(new Baz()); +} + +function test(foo) { + const oStable = foo.oStable("OK") + if (oStable !== "OK") return "false: " + oStable + return foo.o() + foo.k() +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/reservedModuleName.kt b/js/js.translator/testData/box/esModules/export/reservedModuleName.kt index bc3173f5d91..b274ea6027f 100644 --- a/js/js.translator/testData/box/esModules/export/reservedModuleName.kt +++ b/js/js.translator/testData/box/esModules/export/reservedModuleName.kt @@ -1,7 +1,6 @@ -// IGNORE_BACKEND: JS +// DONT_TARGET_EXACT_BACKEND: JS // EXPECTED_REACHABLE_NODES: 1270 // SKIP_MINIFICATION -// INFER_MAIN_MODULE // ES_MODULES // MODULE: if @@ -12,6 +11,8 @@ public fun foo(k: String): String = "O$k" // FILE: entry.mjs // ENTRY_ES_MODULE -import { foo } from "./if/index.js"; +import { foo } from "./reservedModuleName-if_v5.mjs"; -console.assert(foo("K") == "OK"); +export function box() { + return foo("K") +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/export/reservedModuleNameInExportedFile.kt b/js/js.translator/testData/box/esModules/export/reservedModuleNameInExportedFile.kt new file mode 100644 index 00000000000..099f77d6897 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/reservedModuleNameInExportedFile.kt @@ -0,0 +1,19 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// EXPECTED_REACHABLE_NODES: 1270 +// SKIP_MINIFICATION +// ES_MODULES + +// MODULE: if +// FILE: lib.kt +@file:JsExport + +@JsName("foo") +public fun foo(k: String): String = "O$k" + +// FILE: entry.mjs +// ENTRY_ES_MODULE +import { foo } from "./reservedModuleNameInExportedFile-if_v5.mjs"; + +export function box() { + return foo("K") +} diff --git a/js/js.translator/testData/box/esModules/export/vararg.kt b/js/js.translator/testData/box/esModules/export/vararg.kt new file mode 100644 index 00000000000..82bd7e800b7 --- /dev/null +++ b/js/js.translator/testData/box/esModules/export/vararg.kt @@ -0,0 +1,28 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// EXPECTED_REACHABLE_NODES: 1270 +// SKIP_MINIFICATION +// ES_MODULES + +// MODULE: vararg +// FILE: lib.kt +@JsExport +fun uintVararg(vararg uints: UInt): String { + for (u in uints) { + if (u == 0u) return "Failed" + } + + return "OK" +} + +@JsExport +fun uint(a: Int): UInt { + return a.toUInt() +} + +// FILE: main.mjs +// ENTRY_ES_MODULE +import { uint, uintVararg } from "./vararg-vararg_v5.mjs" + +export function box() { + return uintVararg([uint(1), uint(2), uint(3)]) +} diff --git a/js/js.translator/testData/box/esModules/inline/inlinedObjectLiteralIsCheck.kt b/js/js.translator/testData/box/esModules/inline/inlinedObjectLiteralIsCheck.kt index 17b599a0b27..ec9cdcec83f 100644 --- a/js/js.translator/testData/box/esModules/inline/inlinedObjectLiteralIsCheck.kt +++ b/js/js.translator/testData/box/esModules/inline/inlinedObjectLiteralIsCheck.kt @@ -30,6 +30,8 @@ fun testOk(ok: Any): String { // FILE: entry.mjs // ENTRY_ES_MODULE -import { convolutedOk, testOk } from "./main/index.js"; +import { convolutedOk, testOk } from "./inlinedObjectLiteralIsCheck_v5.mjs"; -console.assert(testOk(convolutedOk()) == "OK"); +export function box() { + return testOk(convolutedOk()) +} diff --git a/js/js.translator/testData/box/esModules/jsExport/dataClass.kt b/js/js.translator/testData/box/esModules/jsExport/dataClass.kt index 3a09882fc55..058eb562f02 100644 --- a/js/js.translator/testData/box/esModules/jsExport/dataClass.kt +++ b/js/js.translator/testData/box/esModules/jsExport/dataClass.kt @@ -48,8 +48,5 @@ fun box(): String { return "Fail6: ${res.component2}" } - - - return "OK" } \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/jsExport/dataClass.mjs b/js/js.translator/testData/box/esModules/jsExport/dataClass.mjs index c769ab47c73..8e946a16f95 100644 --- a/js/js.translator/testData/box/esModules/jsExport/dataClass.mjs +++ b/js/js.translator/testData/box/esModules/jsExport/dataClass.mjs @@ -1,4 +1,4 @@ -import { Point } from "./main/index.js"; +import { Point } from "./dataClass_v5.mjs"; export default function() { var p = new Point(3, 7); diff --git a/js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.mjs b/js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.mjs index 1120bb10486..6774f3968a0 100644 --- a/js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.mjs +++ b/js/js.translator/testData/box/esModules/jsExport/exportedDefaultStub.mjs @@ -1,4 +1,4 @@ -import * as api from "./main/index.js"; +import * as api from "./exportedDefaultStub_v5.mjs"; export default function() { var ping = api.ping; diff --git a/js/js.translator/testData/box/esModules/jsExport/jsExportInClass.mjs b/js/js.translator/testData/box/esModules/jsExport/jsExportInClass.mjs index 588071f4959..b1f45dfb36c 100644 --- a/js/js.translator/testData/box/esModules/jsExport/jsExportInClass.mjs +++ b/js/js.translator/testData/box/esModules/jsExport/jsExportInClass.mjs @@ -1,4 +1,4 @@ -import { A, B } from "./main/index.js"; +import { A, B } from "./jsExportInClass_v5.mjs"; export default function() { return { diff --git a/js/js.translator/testData/box/esModules/jsExport/recursiveExport.mjs b/js/js.translator/testData/box/esModules/jsExport/recursiveExport.mjs index ab4c7d895fa..1184282f8b0 100644 --- a/js/js.translator/testData/box/esModules/jsExport/recursiveExport.mjs +++ b/js/js.translator/testData/box/esModules/jsExport/recursiveExport.mjs @@ -1,4 +1,4 @@ -import { ping, Something } from "./main/index.js" +import { ping, Something } from "./recursiveExport_v5.mjs" export default function() { return { diff --git a/js/js.translator/testData/box/esModules/native/inheritanceInNativeClass.kt b/js/js.translator/testData/box/esModules/native/inheritanceInNativeClass.kt index c81dde79f04..d698b9189aa 100644 --- a/js/js.translator/testData/box/esModules/native/inheritanceInNativeClass.kt +++ b/js/js.translator/testData/box/esModules/native/inheritanceInNativeClass.kt @@ -24,7 +24,7 @@ open class B { // FILE: entry.mjs // ENTRY_ES_MODULE -import { A, B } from "./main/index.js"; +import { A, B } from "./inheritanceInNativeClass_v5.mjs"; function createA() { function ADerived() { @@ -46,7 +46,7 @@ function createB() { return new BDerived(); } -function box() { +export function box() { let a = createA(); if (a.bar(0) != 124) return "fail1"; @@ -54,6 +54,4 @@ function box() { if (b.bar(0) != 42) return "fail2"; return "OK"; -} - -console.assert(box() == "OK"); +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/module-systems-in-exported-file/commonjs.d.ts b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/commonjs.d.ts index ae765fd8c67..4fb8e86062e 100644 --- a/js/js.translator/testData/typescript-export/module-systems-in-exported-file/commonjs.d.ts +++ b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/commonjs.d.ts @@ -1,5 +1,5 @@ type Nullable = T | null | undefined -export namespace foo { +export declare namespace foo { const prop: number; class C { constructor(x: number); diff --git a/js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.d.ts b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.d.ts new file mode 100644 index 00000000000..98a24d862de --- /dev/null +++ b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.d.ts @@ -0,0 +1,33 @@ +type Nullable = T | null | undefined +export declare const value: { get(): number; }; +export declare const variable: { get(): number; set(value: number): void; }; +export declare class C { + constructor(x: number); + get x(): number; + doubleX(): number; +} +export declare const O: { + getInstance(): { + get value(): number; + }; +}; +export declare const Parent: { + getInstance(): typeof __NonExistentParent; +}; +declare abstract class __NonExistentParent extends _objects_.foo$Parent { + private constructor(); +} +declare namespace __NonExistentParent { + class Nested { + constructor(); + get value(): number; + } +} +export declare function box(): string; +declare namespace _objects_ { + const foo$Parent: { + get value(): number; + } & { + new(): any; + }; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.kt b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.kt new file mode 100644 index 00000000000..75394282fb7 --- /dev/null +++ b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/esm.kt @@ -0,0 +1,40 @@ +/** This file is generated by {@link :js:js.test:generateJsExportOnFileTestFilesForTS} task. DO NOT MODIFY MANUALLY */ + +// CHECK_TYPESCRIPT_DECLARATIONS +// SKIP_MINIFICATION +// SKIP_NODE_JS +// INFER_MAIN_MODULE +// MODULE: JS_TESTS +// MODULE_KIND: ES +// FILE: esm.kt + +@file:JsExport + +package foo + + +val value = 10 + + +var variable = 10 + + +class C(val x: Int) { + fun doubleX() = x * 2 +} + + +object O { + val value = 10 +} + + +object Parent { + val value = 10 + class Nested { + val value = 10 + } +} + + +fun box(): String = "OK" \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/module-systems-in-exported-file/umd.d.ts b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/umd.d.ts index c5113307e26..157bcd74b5a 100644 --- a/js/js.translator/testData/typescript-export/module-systems-in-exported-file/umd.d.ts +++ b/js/js.translator/testData/typescript-export/module-systems-in-exported-file/umd.d.ts @@ -1,5 +1,5 @@ type Nullable = T | null | undefined -export namespace foo { +export declare namespace foo { const prop: number; class C { constructor(x: number); diff --git a/js/js.translator/testData/typescript-export/module-systems/commonjs.d.ts b/js/js.translator/testData/typescript-export/module-systems/commonjs.d.ts index ae765fd8c67..4fb8e86062e 100644 --- a/js/js.translator/testData/typescript-export/module-systems/commonjs.d.ts +++ b/js/js.translator/testData/typescript-export/module-systems/commonjs.d.ts @@ -1,5 +1,5 @@ type Nullable = T | null | undefined -export namespace foo { +export declare namespace foo { const prop: number; class C { constructor(x: number); diff --git a/js/js.translator/testData/typescript-export/module-systems/esm.d.ts b/js/js.translator/testData/typescript-export/module-systems/esm.d.ts new file mode 100644 index 00000000000..98a24d862de --- /dev/null +++ b/js/js.translator/testData/typescript-export/module-systems/esm.d.ts @@ -0,0 +1,33 @@ +type Nullable = T | null | undefined +export declare const value: { get(): number; }; +export declare const variable: { get(): number; set(value: number): void; }; +export declare class C { + constructor(x: number); + get x(): number; + doubleX(): number; +} +export declare const O: { + getInstance(): { + get value(): number; + }; +}; +export declare const Parent: { + getInstance(): typeof __NonExistentParent; +}; +declare abstract class __NonExistentParent extends _objects_.foo$Parent { + private constructor(); +} +declare namespace __NonExistentParent { + class Nested { + constructor(); + get value(): number; + } +} +export declare function box(): string; +declare namespace _objects_ { + const foo$Parent: { + get value(): number; + } & { + new(): any; + }; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/module-systems/esm.kt b/js/js.translator/testData/typescript-export/module-systems/esm.kt new file mode 100644 index 00000000000..ab5750f0aca --- /dev/null +++ b/js/js.translator/testData/typescript-export/module-systems/esm.kt @@ -0,0 +1,36 @@ +// CHECK_TYPESCRIPT_DECLARATIONS +// SKIP_MINIFICATION +// SKIP_NODE_JS +// INFER_MAIN_MODULE +// MODULE: JS_TESTS +// MODULE_KIND: ES +// FILE: esm.kt + +package foo + +@JsExport +val value = 10 + +@JsExport +var variable = 10 + +@JsExport +class C(val x: Int) { + fun doubleX() = x * 2 +} + +@JsExport +object O { + val value = 10 +} + +@JsExport +object Parent { + val value = 10 + class Nested { + val value = 10 + } +} + +@JsExport +fun box(): String = "OK" \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/module-systems/umd.d.ts b/js/js.translator/testData/typescript-export/module-systems/umd.d.ts index c5113307e26..157bcd74b5a 100644 --- a/js/js.translator/testData/typescript-export/module-systems/umd.d.ts +++ b/js/js.translator/testData/typescript-export/module-systems/umd.d.ts @@ -1,5 +1,5 @@ type Nullable = T | null | undefined -export namespace foo { +export declare namespace foo { const prop: number; class C { constructor(x: number); diff --git a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.d.ts b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.d.ts index 9de9e3184dc..53702d022c4 100644 --- a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.d.ts +++ b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.d.ts @@ -8,6 +8,12 @@ declare namespace JS_TESTS { foo(): number; }; function takesO(o: typeof foo.O): number; + const WithSimpleObjectInside: { + get value(): string; + get SimpleObject(): { + get value(): string; + }; + }; abstract class Parent { private constructor(); } diff --git a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.kt b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.kt index e3b486f9507..8ef93d2c101 100644 --- a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.kt +++ b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects.kt @@ -26,6 +26,14 @@ fun takesO(o: O): Int = O.x + O.foo() +object WithSimpleObjectInside { + val value: String = "WithSimpleObjectInside" + object SimpleObject { + val value: String = "SimpleObject" + } +} + + object Parent { object Nested1 { val value: String = "Nested1" diff --git a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.js b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.js index 4b843859922..1d080f44480 100644 --- a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.js +++ b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.js @@ -7,6 +7,7 @@ var getParent = JS_TESTS.foo.getParent; var createNested1 = JS_TESTS.foo.createNested1; var createNested2 = JS_TESTS.foo.createNested2; var createNested3 = JS_TESTS.foo.createNested3; +var WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -27,5 +28,7 @@ function box() { assert(createNested1() === nested1); assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2); assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3); + assert(WithSimpleObjectInside.value === "WithSimpleObjectInside"); + assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.ts b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.ts index 4d615540a4e..347735a0de5 100644 --- a/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.ts +++ b/js/js.translator/testData/typescript-export/objects-in-exported-file/objects__main.ts @@ -6,6 +6,7 @@ import getParent = JS_TESTS.foo.getParent; import createNested1 = JS_TESTS.foo.createNested1; import createNested2 = JS_TESTS.foo.createNested2; import createNested3 = JS_TESTS.foo.createNested3; +import WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside; function assert(condition: boolean) { if (!condition) { @@ -30,5 +31,8 @@ function box(): string { assert(createNested1() === nested1) assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2) assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3) + + assert(WithSimpleObjectInside.value === "WithSimpleObjectInside"); + assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject"); return "OK"; } \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/objects/objects.d.ts b/js/js.translator/testData/typescript-export/objects/objects.d.ts index 9de9e3184dc..53702d022c4 100644 --- a/js/js.translator/testData/typescript-export/objects/objects.d.ts +++ b/js/js.translator/testData/typescript-export/objects/objects.d.ts @@ -8,6 +8,12 @@ declare namespace JS_TESTS { foo(): number; }; function takesO(o: typeof foo.O): number; + const WithSimpleObjectInside: { + get value(): string; + get SimpleObject(): { + get value(): string; + }; + }; abstract class Parent { private constructor(); } diff --git a/js/js.translator/testData/typescript-export/objects/objects.kt b/js/js.translator/testData/typescript-export/objects/objects.kt index f6da5145a72..cc804d74080 100644 --- a/js/js.translator/testData/typescript-export/objects/objects.kt +++ b/js/js.translator/testData/typescript-export/objects/objects.kt @@ -21,6 +21,14 @@ object O { fun takesO(o: O): Int = O.x + O.foo() +@JsExport +object WithSimpleObjectInside { + val value: String = "WithSimpleObjectInside" + object SimpleObject { + val value: String = "SimpleObject" + } +} + @JsExport object Parent { object Nested1 { diff --git a/js/js.translator/testData/typescript-export/objects/objects__main.js b/js/js.translator/testData/typescript-export/objects/objects__main.js index 4b843859922..1d080f44480 100644 --- a/js/js.translator/testData/typescript-export/objects/objects__main.js +++ b/js/js.translator/testData/typescript-export/objects/objects__main.js @@ -7,6 +7,7 @@ var getParent = JS_TESTS.foo.getParent; var createNested1 = JS_TESTS.foo.createNested1; var createNested2 = JS_TESTS.foo.createNested2; var createNested3 = JS_TESTS.foo.createNested3; +var WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -27,5 +28,7 @@ function box() { assert(createNested1() === nested1); assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2); assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3); + assert(WithSimpleObjectInside.value === "WithSimpleObjectInside"); + assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/objects/objects__main.ts b/js/js.translator/testData/typescript-export/objects/objects__main.ts index 4d615540a4e..347735a0de5 100644 --- a/js/js.translator/testData/typescript-export/objects/objects__main.ts +++ b/js/js.translator/testData/typescript-export/objects/objects__main.ts @@ -6,6 +6,7 @@ import getParent = JS_TESTS.foo.getParent; import createNested1 = JS_TESTS.foo.createNested1; import createNested2 = JS_TESTS.foo.createNested2; import createNested3 = JS_TESTS.foo.createNested3; +import WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside; function assert(condition: boolean) { if (!condition) { @@ -30,5 +31,8 @@ function box(): string { assert(createNested1() === nested1) assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2) assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3) + + assert(WithSimpleObjectInside.value === "WithSimpleObjectInside"); + assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject"); return "OK"; } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt index 97b6e608d76..1a31f70b305 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt @@ -183,4 +183,8 @@ constructor( } } } + + override fun useEsModules() { + error("ES modules are not supported in legacy JS compiler. Please, use IR one instead.") + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt index 926b4c52dc6..861e62f05ee 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt @@ -53,6 +53,7 @@ interface KotlinJsTargetDsl : KotlinTarget { } fun useCommonJs() + fun useEsModules() val binaries: KotlinJsBinaryContainer diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt index a25c1dbe007..54007775a3f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt @@ -358,9 +358,30 @@ constructor( legacyTarget?.useCommonJs() } + override fun useEsModules() { + compilations.all { + it.kotlinOptions.configureEsModulesOptions() + + binaries + .withType(JsIrBinary::class.java) + .all { + it.linkTask.configure { linkTask -> + linkTask.kotlinOptions.configureEsModulesOptions() + } + } + } + + } + private fun KotlinJsOptions.configureCommonJsOptions() { moduleKind = "commonjs" sourceMap = true sourceMapEmbedSources = "never" } + + private fun KotlinJsOptions.configureEsModulesOptions() { + moduleKind = "es" + sourceMap = true + sourceMapEmbedSources = "never" + } }