[K/JS] Move ES modules logic to a new transformer with IC

This commit is contained in:
Artem Kobzar
2022-10-13 07:32:44 +00:00
committed by Space Team
parent 54deba63a1
commit de880ce9aa
88 changed files with 2476 additions and 1134 deletions
@@ -123,7 +123,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
) )
@Argument( @Argument(
value = "-module-kind", value = "-module-kind",
valueDescription = "{plain|amd|commonjs|umd}", valueDescription = "{plain|amd|commonjs|umd|es}",
description = "Kind of the JS module generated by the compiler" description = "Kind of the JS module generated by the compiler"
) )
var moduleKind: String? by NullableStringFreezableVar(K2JsArgumentConstants.MODULE_PLAIN) var moduleKind: String? by NullableStringFreezableVar(K2JsArgumentConstants.MODULE_PLAIN)
@@ -551,7 +551,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
var moduleKind: ModuleKind? = if (moduleKindName != null) moduleKindMap[moduleKindName] else ModuleKind.PLAIN var moduleKind: ModuleKind? = if (moduleKindName != null) moduleKindMap[moduleKindName] else ModuleKind.PLAIN
if (moduleKind == null) { if (moduleKind == null) {
messageCollector.report( 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 moduleKind = ModuleKind.PLAIN
} }
@@ -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<String>?,
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<IrPackageFragment>
abstract val externalPackageFragments: Iterable<IrPackageFragment>
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<IrPackageFragment> =
module.files
override val externalPackageFragments: Iterable<IrPackageFragment> =
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<IrModuleFragment>,
val externalModules: Iterable<IrPackageFragment>
) : CodegenUnit() {
override val packageFragments: Iterable<IrPackageFragment> =
modules.flatMap { it.files }
override val externalPackageFragments: Iterable<IrPackageFragment>
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<JsStatement>,
val exportedDeclarations: List<ExportedDeclaration>,
)
fun generateUnit(unit: CodegenUnit): GeneratedUnit {
val exportedDeclarations: List<ExportedDeclaration> =
with(ExportModelGenerator(backendContext, generateNamespacesForPackages = false)) {
(unit.externalPackageFragments + unit.packageFragments).flatMap { packageFragment ->
generateExport(packageFragment)
}
}
val stableNames: Set<String> = collectStableNames(unit)
val nameGenerator = NewNamerImpl(backendContext, unit, guid, stableNames)
val staticContext = JsStaticContext(
backendContext = backendContext,
irNamer = nameGenerator,
globalNameScope = nameGenerator.staticNames
)
val declarationStatements: List<JsStatement> = 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<JsStatement>()
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<JsExport.Element>()
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<String>(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<String> {
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<JsExpression> = emptyList()
): Pair<JsStatement, JsExpression> {
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<JsExpression> = emptyList()
): List<JsStatement> =
invokeFunctionFromEntryJsFile(function, args)
.let { listOf(it.first, it.second.makeStmt()) }
fun generateModules(
mainModule: IrModuleFragment,
allModules: List<IrModuleFragment>
) {
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<JsStatement>) {
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<IrModuleFragment>,
) {
val moduleName = mainModule.jsModuleName
val indexJsStatements = mutableListOf<JsStatement>()
val exportedDeclarations = mutableListOf<ExportedDeclaration>()
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<JsStatement>.toJsCodeString(): String =
JsCompositeBlock(this).toString()
enum class JsGenerationGranularity {
WHOLE_PROGRAM,
PER_MODULE,
PER_FILE
}
fun generateEsModules(
ir: LoweredIr,
outputSink: CompilerOutputSink,
mainArguments: List<String>?,
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)
}
@@ -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
}
@@ -48,7 +48,7 @@ data class ExportedConstructSignature(
val returnType: ExportedType, val returnType: ExportedType,
) : ExportedDeclaration() ) : ExportedDeclaration()
class ExportedProperty( data class ExportedProperty(
val name: String, val name: String,
val type: ExportedType, val type: ExportedType,
val mutable: Boolean = true, val mutable: Boolean = true,
@@ -94,7 +94,7 @@ data class ExportedObject(
override val members: List<ExportedDeclaration>, override val members: List<ExportedDeclaration>,
override val nestedClasses: List<ExportedClass>, override val nestedClasses: List<ExportedClass>,
override val ir: IrClass, override val ir: IrClass,
val irGetter: IrFunction val irGetter: IrSimpleFunction
) : ExportedClass() ) : ExportedClass()
class ExportedParameter( class ExportedParameter(
@@ -5,8 +5,16 @@
package org.jetbrains.kotlin.ir.backend.js.export 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.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.ir.util.companionObject
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd
@@ -17,8 +25,14 @@ class ExportModelToJsStatements(
) { ) {
private val namespaceToRefMap = mutableMapOf<String, JsNameRef>() private val namespaceToRefMap = mutableMapOf<String, JsNameRef>()
fun generateModuleExport(module: ExportedModule, internalModuleName: JsName): List<JsStatement> { fun generateModuleExport(
return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName), esModules = false) } module: ExportedModule,
internalModuleName: JsName?,
esModules: Boolean
): List<JsStatement> {
return module.declarations.flatMap {
generateDeclarationExport(it, internalModuleName?.makeRef(), esModules)
}
} }
fun generateDeclarationExport( fun generateDeclarationExport(
@@ -60,17 +74,12 @@ class ExportModelToJsStatements(
is ExportedFunction -> { is ExportedFunction -> {
val name = namer.getNameForStaticDeclaration(declaration.ir) val name = namer.getNameForStaticDeclaration(declaration.ir)
if (esModules) { when {
listOf(JsExport(name, alias = JsName(declaration.name, false))) namespace != null ->
} else { listOf(jsAssignment(jsElementAccess(declaration.name, namespace), JsNameRef(name)).makeStmt())
if (namespace != null) {
listOf( esModules -> listOf(JsExport(name, alias = JsName(declaration.name, false)))
jsAssignment( else -> emptyList()
jsElementAccess(declaration.name, namespace),
JsNameRef(name)
).makeStmt()
)
} else emptyList()
} }
} }
@@ -78,39 +87,76 @@ class ExportModelToJsStatements(
is ExportedConstructSignature -> emptyList() is ExportedConstructSignature -> emptyList()
is ExportedProperty -> { is ExportedProperty -> {
require(namespace != null) { "Only namespaced properties are allowed" } require(namespace != null || esModules) { "Only namespaced properties are allowed" }
val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) } val getter = declaration.irGetter?.let { namer.getNameForStaticDeclaration(it) }
val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) } val setter = declaration.irSetter?.let { namer.getNameForStaticDeclaration(it) }
listOf(defineProperty(namespace, declaration.name, getter, setter, namer).makeStmt()) 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 ErrorDeclaration -> emptyList()
is ExportedObject -> { is ExportedObject -> {
require(namespace != null) { "Only namespaced properties are allowed" } require(namespace != null || esModules) { "Only namespaced properties are allowed" }
val newNameSpace = jsElementAccess(declaration.name, namespace) val newNameSpace = when {
val getter = JsNameRef(namer.getNameForStaticDeclaration(declaration.irGetter)) 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) } 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 -> { is ExportedRegularClass -> {
if (declaration.isInterface) return emptyList() 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 name = namer.getNameForStaticDeclaration(declaration.ir)
val klassExport = val newNameSpace = when {
if (esModules) { namespace != null -> jsElementAccess(declaration.name, namespace)
JsExport(name, alias = JsName(declaration.name, false)) esModules -> name.makeRef()
} else { else -> prototypeOf(namer.getNameForClass(declaration.ir).makeRef(), namer)
if (namespace != null) { }
jsAssignment( val klassExport = when {
newNameSpace, namespace != null -> jsAssignment(newNameSpace, JsNameRef(name)).makeStmt()
JsNameRef(name) esModules -> JsExport(name, alias = JsName(declaration.name, false))
).makeStmt() else -> null
} else null
} }
// These are only used when exporting secondary constructors annotated with @JsName // These are only used when exporting secondary constructors annotated with @JsName
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.ir.backend.js.export package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities 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.getFqNameWithJsNameWhenAvailable
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName 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.js.common.isValidES5Identifier
import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.runIf
import javax.lang.model.type.IntersectionType
private const val Nullable = "Nullable" private const val Nullable = "Nullable"
private const val objects = "_objects_" 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 = '$' private const val syntheticObjectNameSeparator = '$'
fun ExportedModule.toTypeScript(): String { fun ExportedModule.toTypeScript(): String {
@@ -61,32 +65,36 @@ class ExportModelToTsDeclarations {
return joinToString("\n") { return joinToString("\n") {
it.toTypeScript( it.toTypeScript(
indent = moduleKind.indent, indent = moduleKind.indent,
prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export " prefix = if (moduleKind == ModuleKind.PLAIN) "" else declareExorted,
esModules = moduleKind == ModuleKind.ES
)
} + generateObjectsNamespaceIfNeeded(
indent = moduleKind.indent,
prefix = if (moduleKind == ModuleKind.PLAIN) "" else declare,
) )
} + generateObjectsNamespaceIfNeeded(moduleKind.indent)
} }
private fun generateObjectsNamespaceIfNeeded(indent: String): String { private fun generateObjectsNamespaceIfNeeded(indent: String, prefix: String): String {
return if (objectsSyntheticProperties.isEmpty()) { return if (objectsSyntheticProperties.isEmpty()) {
"" ""
} else { } else {
"\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, "") "\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, prefix)
} }
} }
private fun List<ExportedDeclaration>.toTypeScript(indent: String): String = private fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
joinToString("") { it.toTypeScript(indent) + "\n" } 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) { indent + when (this) {
is ErrorDeclaration -> generateTypeScriptString() is ErrorDeclaration -> generateTypeScriptString()
is ExportedNamespace -> generateTypeScriptString(indent, prefix)
is ExportedFunction -> generateTypeScriptString(indent, prefix)
is ExportedConstructor -> generateTypeScriptString(indent) is ExportedConstructor -> generateTypeScriptString(indent)
is ExportedConstructSignature -> generateTypeScriptString(indent) is ExportedConstructSignature -> generateTypeScriptString(indent)
is ExportedProperty -> generateTypeScriptString(indent, prefix) is ExportedNamespace -> generateTypeScriptString(indent, prefix)
is ExportedObject -> generateTypeScriptString(indent, prefix) is ExportedFunction -> generateTypeScriptString(indent, prefix)
is ExportedRegularClass -> 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 { private fun ErrorDeclaration.generateTypeScriptString(): String {
@@ -107,34 +115,45 @@ class ExportModelToTsDeclarations {
return "new($renderedParameters): ${returnType.toTypeScript(indent)};" return "new($renderedParameters): ${returnType.toTypeScript(indent)};"
} }
private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String): String { private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean = false): String {
val visibility = if (isProtected) "protected " else "" val extraIndent = "$indent "
val keyword = when {
isMember -> (if (isAbstract) "abstract " else "")
else -> if (mutable) "let " else "const "
}
val possibleStatic = if (isMember && isStatic) "static " else ""
val containsUnresolvedChar = !name.isValidES5Identifier()
val memberName = when {
isMember && containsUnresolvedChar -> "\"$name\""
else -> name
}
val typeToTypeScript = type.toTypeScript(indent)
return if (isMember && !isField) {
val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;"
if (!mutable) {
getter
} else {
getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);"
}
} else {
if (!isMember && containsUnresolvedChar) {
""
} else {
val readonly = if (isMember && !mutable) "readonly " else ""
val optional = if (isOptional) "?" else "" val optional = if (isOptional) "?" else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName$optional: $typeToTypeScript;" val containsUnresolvedChar = !name.isValidES5Identifier()
val memberName = if (containsUnresolvedChar) "\"$name\"" else name
val isObjectGetter = irGetter?.origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION
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 {
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 {
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) { return if (!isMember && containsUnresolvedChar) {
"" ""
} else { } 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() 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) { for (superInterface in superClasses + superInterfaces) {
t = ExportedType.IntersectionType(t, superInterface) t = ExportedType.IntersectionType(t, superInterface)
@@ -208,13 +232,14 @@ class ExportModelToTsDeclarations {
) )
return if (!shouldRenderSeparatedAbstractClass) { return if (!shouldRenderSeparatedAbstractClass) {
property.generateTypeScriptString(indent, prefix) property.generateTypeScriptString(indent, prefix, esModules)
} else { } else {
val className = NonExistent.takeIf { esModules }.orEmpty() + name
val propertyRef = "$objects.$propertyName" val propertyRef = "$objects.$propertyName"
val shouldCreateExtraProperty = members.isNotEmpty() || superInterfaces.isNotEmpty() || superClasses.isNotEmpty() val shouldCreateExtraProperty = members.isNotEmpty() || superInterfaces.isNotEmpty() || superClasses.isNotEmpty()
val newSuperClass = ExportedType.ClassType(propertyRef, emptyList(), ir).takeIf { shouldCreateExtraProperty } val newSuperClass = ExportedType.ClassType(propertyRef, emptyList(), ir).takeIf { shouldCreateExtraProperty }
ExportedRegularClass( val classForRender = ExportedRegularClass(
name = name, name = className,
isInterface = false, isInterface = false,
isAbstract = true, isAbstract = true,
superClasses = listOfNotNull(newSuperClass), superClasses = listOfNotNull(newSuperClass),
@@ -224,8 +249,14 @@ class ExportModelToTsDeclarations {
nestedClasses = nestedClasses, nestedClasses = nestedClasses,
ir = ir ir = ir
) )
.generateTypeScriptString(indent, prefix)
.also { if (shouldCreateExtraProperty) objectsSyntheticProperties.add(property) } .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 = val klassExport =
"$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}" "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
val staticsExport = val staticsExport =
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript( if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
indent,
prefix
) else ""
return if (name.isValidES5Identifier()) klassExport + staticsExport else "" return if (name.isValidES5Identifier()) klassExport + staticsExport else ""
} }
@@ -42,7 +42,7 @@ class JsExecutableProducer(
val jsMultiModuleCache = JsMultiModuleCache(caches) val jsMultiModuleCache = JsMultiModuleCache(caches)
val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache() val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache()
val resolver = CrossModuleDependenciesResolver(cachedProgram.map { it.jsIrHeader }) val resolver = CrossModuleDependenciesResolver(moduleKind, cachedProgram.map { it.jsIrHeader })
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath) val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences) jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences)
@@ -159,7 +159,7 @@ class IrModuleToJsTransformer(
val internalModuleName = ReservedJsNames.makeInternalModuleName() val internalModuleName = ReservedJsNames.makeInternalModuleName()
val globalNames = NameTable<String>(namer.globalNames) val globalNames = NameTable<String>(namer.globalNames)
val exportStatements = ExportModelToJsStatements(staticContext) { globalNames.declareFreshName(it, it) } 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 (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it), false) })
val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName) val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName)
@@ -94,6 +94,7 @@ class IrModuleToJsTransformerTmp(
private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
private val isEsModules = moduleKind == ModuleKind.ES
private val sourceMapInfo = SourceMapsInfo.from(backendContext.configuration) private val sourceMapInfo = SourceMapsInfo.from(backendContext.configuration)
private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List<Pair<IrFile, List<ExportedDeclaration>>>) private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List<Pair<IrFile, List<ExportedDeclaration>>>)
@@ -103,7 +104,7 @@ class IrModuleToJsTransformerTmp(
} }
private fun associateIrAndExport(modules: Iterable<IrModuleFragment>): List<IrAndExportedDeclarations> { private fun associateIrAndExport(modules: Iterable<IrModuleFragment>): List<IrAndExportedDeclarations> {
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules)
return modules.map { module -> return modules.map { module ->
val files = module.files.map { file -> val files = module.files.map { file ->
@@ -166,7 +167,7 @@ class IrModuleToJsTransformerTmp(
} }
fun generateBinaryAst(files: Collection<IrFile>, allModules: Collection<IrModuleFragment>): List<JsIrFragmentAndBinaryAst> { fun generateBinaryAst(files: Collection<IrFile>, allModules: Collection<IrModuleFragment>): List<JsIrFragmentAndBinaryAst> {
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true) val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules)
val exportData = files.map { it to exportModelGenerator.generateExportWithExternals(it) } val exportData = files.map { it to exportModelGenerator.generateExportWithExternals(it) }
@@ -235,12 +236,13 @@ class IrModuleToJsTransformerTmp(
polyfills.statements += backendContext.polyfills.getAllPolyfillsFor(file) polyfills.statements += backendContext.polyfills.getAllPolyfillsFor(file)
} }
val internalModuleName = ReservedJsNames.makeInternalModuleName() val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules }
val globalNames = NameTable<String>(globalNameScope) val globalNames = NameTable<String>(globalNameScope)
val exportStatements = val exportStatements =
ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport( ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
ExportedModule(mainModuleName, moduleKind, exports), ExportedModule(mainModuleName, moduleKind, exports),
internalModuleName, internalModuleName,
isEsModules
) )
result.exports.statements += exportStatements result.exports.statements += exportStatements
@@ -379,7 +381,7 @@ private fun generateWrappedModuleBody(
// mutable container allows explicitly remove elements from itself, // mutable container allows explicitly remove elements from itself,
// so we are able to help GC to free heavy JsIrModule objects // 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 // 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) -> val mainModule = moduleToRef.removeLast().let { (main, mainRef) ->
generateSingleWrappedModuleBody( generateSingleWrappedModuleBody(
mainModuleName, mainModuleName,
@@ -433,7 +435,7 @@ fun generateSingleWrappedModuleBody(
sourceMapsInfo: SourceMapsInfo?, sourceMapsInfo: SourceMapsInfo?,
generateScriptModule: Boolean, generateScriptModule: Boolean,
generateCallToMain: Boolean, generateCallToMain: Boolean,
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty, crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty(moduleKind),
outJsProgram: Boolean = true outJsProgram: Boolean = true
): CompilationOutputs { ): CompilationOutputs {
val program = Merger( val program = Merger(
@@ -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.ir.backend.js.utils.toJsIdentifier
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import java.io.File import java.io.File
import org.jetbrains.kotlin.serialization.js.ModuleKind
class JsIrProgramFragment(val packageFqn: String) { class JsIrProgramFragment(val packageFqn: String) {
val nameBindings = mutableMapOf<String, JsName>() val nameBindings = mutableMapOf<String, JsName>()
@@ -57,8 +58,8 @@ class JsIrModuleHeader(
} }
class JsIrProgram(private var modules: List<JsIrModule>) { class JsIrProgram(private var modules: List<JsIrModule>) {
fun asCrossModuleDependencies(relativeRequirePath: Boolean): List<Pair<JsIrModule, CrossModuleReferences>> { fun asCrossModuleDependencies(moduleKind: ModuleKind, relativeRequirePath: Boolean): List<Pair<JsIrModule, CrossModuleReferences>> {
val resolver = CrossModuleDependenciesResolver(modules.map { it.makeModuleHeader() }) val resolver = CrossModuleDependenciesResolver(moduleKind, modules.map { it.makeModuleHeader() })
modules = emptyList() modules = emptyList()
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath) val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
return crossModuleReferences.entries.map { return crossModuleReferences.entries.map {
@@ -75,9 +76,12 @@ class JsIrProgram(private var modules: List<JsIrModule>) {
} }
} }
class CrossModuleDependenciesResolver(private val headers: List<JsIrModuleHeader>) { class CrossModuleDependenciesResolver(
private val moduleKind: ModuleKind,
private val headers: List<JsIrModuleHeader>
) {
fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> { fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> {
val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) } val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(moduleKind, it, relativeRequirePath) }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>() val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
val mainModuleHeader = headers.last() 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 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<CrossModuleRef>() val imports = mutableListOf<CrossModuleRef>()
val exports = mutableSetOf<String>() val exports = mutableSetOf<String>()
var transitiveJsExportFrom = emptyList<JsIrModuleHeader>() var transitiveJsExportFrom = emptyList<JsIrModuleHeader>()
@@ -155,7 +163,13 @@ private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHead
val transitiveExport = transitiveJsExportFrom.mapNotNull { val transitiveExport = transitiveJsExportFrom.mapNotNull {
if (it.hasJsExports) import(it) else null 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? { 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 CrossModuleImport(val exportedAs: String, val moduleExporter: JsName)
class CrossModuleReferences( class CrossModuleReferences(
val moduleKind: ModuleKind,
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
val transitiveJsExportFrom: List<JsName>, // the list of modules which provide their js exports for transitive export val transitiveJsExportFrom: List<JsName>, // the list of modules which provide their js exports for transitive export
val exports: Map<String, String>, // tag -> index val exports: Map<String, String>, // tag -> index
@@ -190,12 +205,21 @@ class CrossModuleReferences(
val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value } val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value }
jsImports = imports.entries.associate { jsImports = imports.entries.associate {
val importedAs = tagToName[it.key] ?: error("Internal error: cannot find imported name for symbol ${it.key.prettyTag()}") 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) it.key to JsVars.JsVar(importedAs, exportRef)
} }
} }
companion object { companion object {
val Empty = CrossModuleReferences(listOf(), emptyList(), emptyMap(), emptyMap()) fun Empty(moduleKind: ModuleKind) = CrossModuleReferences(moduleKind, listOf(), emptyList(), emptyMap(), emptyMap())
} }
} }
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.partitionIsInstance
class Merger( class Merger(
private val moduleName: String, private val moduleName: String,
@@ -20,6 +21,7 @@ class Merger(
private val generateCallToMain: Boolean, private val generateCallToMain: Boolean,
) { ) {
private val isEsModules = moduleKind == ModuleKind.ES
private val importStatements = mutableMapOf<String, JsStatement>() private val importStatements = mutableMapOf<String, JsStatement>()
private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>() private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>()
@@ -65,6 +67,14 @@ class Merger(
if (crossModuleReferences.exports.isNotEmpty()) { if (crossModuleReferences.exports.isNotEmpty()) {
val internalModuleName = ReservedJsNames.makeInternalModuleName() val internalModuleName = ReservedJsNames.makeInternalModuleName()
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))
}
additionalExports += JsExport(JsExport.Subject.Elements(exportedElements))
} else {
val createExportBlock = jsAssignment( val createExportBlock = jsAssignment(
ReservedJsNames.makeCrossModuleNameRef(internalModuleName), ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral()) JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
@@ -78,6 +88,7 @@ class Merger(
} }
} }
} }
}
private fun JsIrProgramFragment.buildRenames(nameMap: MutableMap<String, JsName>): Map<JsName, JsName> { private fun JsIrProgramFragment.buildRenames(nameMap: MutableMap<String, JsName>): Map<JsName, JsName> {
val result = mutableMapOf<JsName, JsName>() val result = mutableMapOf<JsName, JsName>()
@@ -125,6 +136,21 @@ class Merger(
} }
private fun declareAndCallJsExporter(): List<JsStatement> { private fun declareAndCallJsExporter(): List<JsStatement> {
if (isEsModules) {
val allExportRelatedStatements = fragments.flatMap { it.exports.statements }
val (allExportStatements, restStatements) = allExportRelatedStatements.partitionIsInstance<JsStatement, JsExport>()
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 oneLargeExportStatement = exportedElements?.let { JsExport(JsExport.Subject.Elements(it)) }
return restStatements + listOfNotNull(oneLargeExportStatement) + restExportStatements
} else {
val exportBody = JsBlock(fragments.flatMap { it.exports.statements }) val exportBody = JsBlock(fragments.flatMap { it.exports.statements })
if (exportBody.isEmpty) { if (exportBody.isEmpty) {
return emptyList() return emptyList()
@@ -145,6 +171,7 @@ class Merger(
} }
return result return result
} }
}
private fun transitiveJsExport(): List<JsStatement> { private fun transitiveJsExport(): List<JsStatement> {
val internalModuleName = ReservedJsNames.makeInternalModuleName() val internalModuleName = ReservedJsNames.makeInternalModuleName()
@@ -214,9 +241,6 @@ class Merger(
if (generateScriptModule) { if (generateScriptModule) {
with(program.globalBlock) { with(program.globalBlock) {
if (!generateScriptModule) {
statements += JsStringLiteral("use strict").makeStmt()
}
statements.addWithComment("block: polyfills", polyfillDeclarationBlock.statements) statements.addWithComment("block: polyfills", polyfillDeclarationBlock.statements)
statements.addWithComment("block: imports", importStatements) statements.addWithComment("block: imports", importStatements)
statements += moduleBody statements += moduleBody
@@ -228,7 +252,7 @@ class Merger(
parameters += JsParameter(internalModuleName) parameters += JsParameter(internalModuleName)
parameters += (importedJsModules).map { JsParameter(it.internalName) } parameters += (importedJsModules).map { JsParameter(it.internalName) }
with(body) { with(body) {
if (!generateScriptModule) { if (!isEsModules) {
statements += JsStringLiteral("use strict").makeStmt() statements += JsStringLiteral("use strict").makeStmt()
} }
statements.addWithComment("block: imports", importStatements) statements.addWithComment("block: imports", importStatements)
@@ -17,7 +17,7 @@ object ModuleWrapperTranslation {
} }
fun wrap( fun wrap(
moduleId: String, function: JsExpression, importedModules: List<JsImportedModule>, moduleId: String, function: JsFunction, importedModules: List<JsImportedModule>,
program: JsProgram, kind: ModuleKind program: JsProgram, kind: ModuleKind
): List<JsStatement> { ): List<JsStatement> {
return when (kind) { return when (kind) {
@@ -25,7 +25,7 @@ object ModuleWrapperTranslation {
ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program) ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program)
ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program) ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program)
ModuleKind.PLAIN -> wrapPlain(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()) return listOf(invocation.makeStmt())
} }
private fun wrapEsModule(function: JsFunction, importedModules: List<JsImportedModule>): List<JsStatement> {
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( private fun wrapPlain(
moduleId: String, function: JsExpression, moduleId: String, function: JsExpression,
importedModules: List<JsImportedModule>, program: JsProgram importedModules: List<JsImportedModule>, program: JsProgram
@@ -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<IrDeclaration, Int>()
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<String>()
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<String>,
) : IrNamerBase() {
val staticNames = NameTable<IrDeclaration>(
reserved = stableNames.toMutableSet()
)
val internalImports = mutableMapOf<String, JsImport>()
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
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ where possible options include:
-libraries <path> Paths to Kotlin libraries with .meta.js and .kjsm files, separated by system path separator -libraries <path> 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 -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 -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 Kind of the JS module generated by the compiler
-no-stdlib Don't automatically include the default Kotlin/JS stdlib into compilation dependencies -no-stdlib Don't automatically include the default Kotlin/JS stdlib into compilation dependencies
-output <filepath> Destination *.js file for the compilation result -output <filepath> Destination *.js file for the compilation result
@@ -34,8 +34,6 @@ object BinaryArtifacts {
class JsIrArtifact(override val outputFile: File, val compilerResult: CompilerResult, val icCache: Map<String, ByteArray>? = null) : Js() class JsIrArtifact(override val outputFile: File, val compilerResult: CompilerResult, val icCache: Map<String, ByteArray>? = null) : Js()
class JsEsArtifact(override val outputFile: File, val outputDceFile: File?) : Js()
data class IncrementalJsArtifact(val originalArtifact: Js, val recompiledArtifact: Js) : Js() { data class IncrementalJsArtifact(val originalArtifact: Js, val recompiledArtifact: Js) : Js() {
override val outputFile: File override val outputFile: File
get() = unwrap().outputFile get() = unwrap().outputFile
@@ -1329,15 +1329,27 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitImport(@NotNull JsImport jsImport) { public void visitImport(@NotNull JsImport jsImport) {
p.print("import {"); JsImport.Target target = jsImport.getTarget();
boolean isMultiline = jsImport.getElements().size() > 1;
p.print("import ");
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<JsImport.Element> elements = ((JsImport.Target.Elements) target).getElements();
p.print("{");
boolean isMultiline = elements.size() > 1;
p.indentIn(); p.indentIn();
if (isMultiline) if (isMultiline)
newlineOpt(); newlineOpt();
else else
space(); space();
for (JsImport.Element element : jsImport.getElements()) { for (JsImport.Element element : elements) {
nameDef(element.getName()); nameDef(element.getName());
JsName alias = element.getAlias(); JsName alias = element.getAlias();
if (alias != null) { if (alias != null) {
@@ -1348,12 +1360,16 @@ public class JsToStringGenerationVisitor extends JsVisitor {
if (isMultiline) { if (isMultiline) {
p.print(','); p.print(',');
newlineOpt(); newlineOpt();
} else { }
else {
space(); space();
} }
} }
p.indentOut(); p.indentOut();
p.print("} from "); p.print("}");
}
p.print(" from ");
p.print(javaScriptString(jsImport.getModule())); p.print(javaScriptString(jsImport.getModule()));
} }
@@ -7,8 +7,23 @@ package org.jetbrains.kotlin.js.backend.ast
class JsImport( class JsImport(
val module: String, val module: String,
val elements: MutableList<Element> = mutableListOf(), val target: Target,
) : SourceInfoAwareJsNode(), JsStatement { ) : SourceInfoAwareJsNode(), JsStatement {
constructor(module: String, elements: MutableList<Element> = mutableListOf()) : this(module, Target.Elements(elements))
val elements: MutableList<Element>
get() = (target as Target.Elements).elements
sealed class Target {
class Elements(val elements: MutableList<Element>) : 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( class Element(
val name: JsName, val name: JsName,
@@ -25,7 +40,7 @@ class JsImport(
} }
override fun deepCopy(): JsStatement = override fun deepCopy(): JsStatement =
JsImport(module, elements.map { it }.toMutableList()) JsImport(module, target)
override fun traverse(v: JsVisitorWithContext, ctx: JsContext<*>) { override fun traverse(v: JsVisitorWithContext, ctx: JsContext<*>) {
v.visit(this, ctx) v.visit(this, ctx)
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.text.StringUtil
private val LINE_SEPARATOR = System.getProperty("line.separator")!! private val LINE_SEPARATOR = System.getProperty("line.separator")!!
private val END_MARKER = "<END>$LINE_SEPARATOR" private val END_MARKER = "<END>$LINE_SEPARATOR"
private val ESM_EXTENSION = ".mjs"
abstract class ProcessBasedScriptEngine( abstract class ProcessBasedScriptEngine(
private val executablePath: String private val executablePath: String
@@ -58,6 +59,7 @@ abstract class ProcessBasedScriptEngine(
} }
override fun loadFile(path: String) { override fun loadFile(path: String) {
if (path.endsWith(ESM_EXTENSION)) return
eval("load('${path.replace('\\', '/')}');") eval("load('${path.replace('\\', '/')}');")
} }
@@ -75,7 +75,8 @@ function restoreGlobalState() {
resetRealm(); resetRealm();
// noinspection InfiniteLoopJS // noinspection InfiniteLoopJS
while (true) { async function loop() {
while (true) {
let code = readline().replace(/\\n/g, '\n'); let code = readline().replace(/\\n/g, '\n');
try { try {
@@ -90,7 +91,7 @@ while (true) {
restoreGlobalState(); restoreGlobalState();
break; break;
default: default:
print(Realm.eval(currentRealmIndex, code)); print(await Realm.eval(currentRealmIndex, code));
} }
} catch(e) { } catch(e) {
printErr(e.stack != null ? e.stack : e.toString()); printErr(e.stack != null ? e.stack : e.toString());
@@ -98,4 +99,7 @@ while (true) {
} }
print('<END>'); print('<END>');
}
} }
loop()
@@ -23,4 +23,7 @@ object JavaScript {
const val EXTENSION = "js" const val EXTENSION = "js"
const val DOT_EXTENSION = "." + EXTENSION const val DOT_EXTENSION = "." + EXTENSION
const val MODULE_EXTENSION = "mjs"
const val DOT_MODULE_EXTENSION = "." + MODULE_EXTENSION
} }
@@ -49,7 +49,7 @@ public class AntTaskJsTest extends AbstractAntTaskTest {
List<String> filePaths = CollectionsKt.map(fileNames, s -> getOutputFileByName(s).getAbsolutePath()); List<String> 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 { private void doJsAntTestForPostfixPrefix(@Nullable String prefix, @Nullable String postfix) throws Exception {
@@ -51,9 +51,7 @@ class ClassicJsBackendFacade(
"$KOTLIN_TEST_INTERNAL.setModuleId(\"$escapedModuleId\"); }\n" + "$KOTLIN_TEST_INTERNAL.setModuleId(\"$escapedModuleId\"); }\n" +
"$content\n" "$content\n"
ModuleKind.PLAIN -> content ModuleKind.PLAIN, ModuleKind.ES -> content
ModuleKind.ES -> error("Module emulation markers are not supported for ES modules")
} }
} }
} }
@@ -12,36 +12,29 @@ import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureDe
import org.jetbrains.kotlin.cli.common.isWindows import org.jetbrains.kotlin.cli.common.isWindows
import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration 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.*
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.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.ic.JsExecutableProducer
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc 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.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp 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.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode 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.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.irMessageLogger import org.jetbrains.kotlin.ir.util.irMessageLogger
import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.test.handlers.JsBoxRunner.Companion.TEST_FUNCTION 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.extractTestPackage
import org.jetbrains.kotlin.js.test.utils.jsIrIncrementalDataProvider import org.jetbrains.kotlin.js.test.utils.jsIrIncrementalDataProvider
import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.name.FqName 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.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.DebugMode import org.jetbrains.kotlin.test.DebugMode
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives 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.frontend.classic.moduleDescriptorProvider
import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.* 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 org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
import java.io.File import java.io.File
const val REGULAR_EXTENSION = ".js"
const val ESM_EXTENSION = ".mjs"
class JsIrBackendFacade( class JsIrBackendFacade(
val testServices: TestServices, val testServices: TestServices,
private val firstTimeCompilation: Boolean private val firstTimeCompilation: Boolean
@@ -91,16 +87,16 @@ class JsIrBackendFacade(
else -> JsGenerationGranularity.WHOLE_PROGRAM else -> JsGenerationGranularity.WHOLE_PROGRAM
} }
val testPackage = extractTestPackage(testServices) val testPackage = extractTestPackage(testServices, ignoreEsModules = false)
val skipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE in module.directives val skipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE in module.directives
if (skipRegularMode) return null if (skipRegularMode) return null
if (JsEnvironmentConfigurator.incrementalEnabled(testServices)) { if (JsEnvironmentConfigurator.incrementalEnabled(testServices)) {
val outputFile = if (firstTimeCompilation) { val outputFile = if (firstTimeCompilation) {
File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + ".js") File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name) + module.kind.extension)
} else { } else {
File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name) + ".js") File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name) + module.kind.extension)
} }
val compiledModule = CompilerResult( val compiledModule = CompilerResult(
@@ -136,11 +132,12 @@ class JsIrBackendFacade(
PhaseConfig(jsPhases) PhaseConfig(jsPhases)
} }
val loweredIr = compileIr( val loweredIr = compileIr(
irModuleFragment, irModuleFragment.apply { resolveTestPathes() },
MainModule.Klib(inputArtifact.outputFile.absolutePath), MainModule.Klib(inputArtifact.outputFile.absolutePath),
configuration, configuration,
dependencyModules, dependencyModules.apply { forEach { it.resolveTestPathes() } },
emptyMap(), emptyMap(),
irModuleFragment.irBuiltins, irModuleFragment.irBuiltins,
symbolTable, symbolTable,
@@ -164,22 +161,27 @@ class JsIrBackendFacade(
module: TestModule, module: TestModule,
loweredIr: LoweredIr, loweredIr: LoweredIr,
granularity: JsGenerationGranularity, granularity: JsGenerationGranularity,
): BinaryArtifacts.Js? { ): BinaryArtifacts.Js {
val generateDts = JsEnvironmentConfigurationDirectives.GENERATE_DTS in module.directives
val mainArguments = JsEnvironmentConfigurator.getMainCallParametersForModule(module) val mainArguments = JsEnvironmentConfigurator.getMainCallParametersForModule(module)
.run { if (shouldBeGenerated()) arguments() else null } .run { if (shouldBeGenerated()) arguments() else null }
val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in module.directives val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in module.directives
val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_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 runNewIr2Js = JsEnvironmentConfigurationDirectives.RUN_NEW_IR_2_JS in module.directives
val perModuleOnly = JsEnvironmentConfigurationDirectives.SPLIT_PER_MODULE 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) + module.kind.extension)
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) { if (runNewIr2Js) {
val transformer = IrModuleToJsTransformerTmp(loweredIr.context, mainArguments) val transformer = IrModuleToJsTransformerTmp(
loweredIr.context,
mainArguments,
moduleToName = JsIrModuleToPath(
testServices,
isEsModules && granularity != JsGenerationGranularity.WHOLE_PROGRAM
)
)
// If runIrDce then include DCE results // If runIrDce then include DCE results
// If perModuleOnly then skip whole program // If perModuleOnly then skip whole program
// (it.dce => runIrDce) && (perModuleOnly => it.perModule) // (it.dce => runIrDce) && (perModuleOnly => it.perModule)
@@ -203,15 +205,10 @@ class JsIrBackendFacade(
} }
} }
val options = JsGenerationOptions(generatePackageJson = true, generateTypeScriptDefinitions = generateDts) private fun IrModuleFragment.resolveTestPathes() {
generateEsModules(loweredIr, jsOutputSink(outputFile.parentFile.esModulesSubDir), mainArguments, granularity, options) JsIrPathReplacer(testServices).let {
files.forEach(it::lower)
if (runIrDce) {
eliminateDeadDeclarations(loweredIr.allModules, loweredIr.context)
generateEsModules(loweredIr, jsOutputSink(dceOutputFile.parentFile.esModulesSubDir), mainArguments, granularity, options)
return BinaryArtifacts.Js.JsEsArtifact(outputFile, dceOutputFile).dump(module)
} }
return BinaryArtifacts.Js.JsEsArtifact(outputFile, null).dump(module)
} }
private fun loadIrFromKlib(module: TestModule, configuration: CompilerConfiguration): IrModuleInfo { private fun loadIrFromKlib(module: TestModule, configuration: CompilerConfiguration): IrModuleInfo {
@@ -238,55 +235,10 @@ class JsIrBackendFacade(
) { if (it == mainModuleLib) moduleDescriptor else testServices.jsLibraryProvider.getDescriptorByCompiledLibrary(it) } ) { if (it == mainModuleLib) moduleDescriptor else testServices.jsLibraryProvider.getDescriptorByCompiledLibrary(it) }
} }
private fun loadIrFromSources( private fun BinaryArtifacts.Js.JsIrArtifact.dump(
module: TestModule, module: TestModule,
configuration: CompilerConfiguration, firstTimeCompilation: Boolean = true
inputArtifact: ClassicFrontendOutputArtifact ): BinaryArtifacts.Js.JsIrArtifact {
): 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 {
val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
val moduleId = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME) val moduleId = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)
val moduleKind = configuration.get(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN) val moduleKind = configuration.get(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
@@ -297,9 +249,15 @@ class JsIrBackendFacade(
if (dontSkipRegularMode) { if (dontSkipRegularMode) {
for ((mode, output) in compilerResult.outputs.entries) { for ((mode, output) in compilerResult.outputs.entries) {
val outputFile = if (firstTimeCompilation) { val outputFile = if (firstTimeCompilation) {
File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, mode) + ".js") File(JsEnvironmentConfigurator.getJsModuleArtifactPath(testServices, module.name, mode) + moduleKind.extension)
} else { } else {
File(JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(testServices, module.name, mode) + ".js") File(
JsEnvironmentConfigurator.getRecompiledJsModuleArtifactPath(
testServices,
module.name,
mode
) + moduleKind.extension
)
} }
output.writeTo(outputFile, moduleId, moduleKind) output.writeTo(outputFile, moduleId, moduleKind)
} }
@@ -307,24 +265,13 @@ class JsIrBackendFacade(
if (generateDts) { if (generateDts) {
outputFile outputFile
.withReplacedExtensionOrNull("_v5.js", ".d.ts")!! .withReplacedExtensionOrNull("_v5${moduleKind.extension}", ".d.ts")!!
.write(compilerResult.tsDefinitions ?: error("No ts definitions")) .write(compilerResult.tsDefinitions ?: error("No ts definitions"))
} }
return this 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) { private fun CompilationOutputs.writeTo(outputFile: File, moduleId: String, moduleKind: ModuleKind) {
val wrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, moduleId = moduleId, moduleKind = moduleKind) val wrappedCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(jsCode, moduleId = moduleId, moduleKind = moduleKind)
outputFile.write(wrappedCode) outputFile.write(wrappedCode)
@@ -344,32 +291,37 @@ class JsIrBackendFacade(
writeText(text) 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 { override fun shouldRunAnalysis(module: TestModule): Boolean {
return JsEnvironmentConfigurator.isMainModule(module, testServices) 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 { fun String.augmentWithModuleName(moduleName: String): String {
check(endsWith("_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 } val normalizedName = moduleName.run { if (isWindows) minify() else this }
return removeSuffix("_v5.js") + "-${normalizedName}_v5.js"
return removeSuffix("_v5$suffix") + "-${normalizedName}_v5$suffix"
}
} }
// D8 ignores Windows settings related to extending of maximum path symbols count // 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") .replace("_minimal_for_test", "_min")
} }
private fun String.isPath(): Boolean = contains("/")
fun File.augmentWithModuleName(moduleName: String): File = File(absolutePath.augmentWithModuleName(moduleName)) fun File.augmentWithModuleName(moduleName: String): File = File(absolutePath.augmentWithModuleName(moduleName))
@@ -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<K, V> {
override val size = if (!shouldProvidePaths) 0 else 1
override val entries = emptySet<Map.Entry<K, V>>()
override val keys = emptySet<K>()
override val values = emptyList<V>()
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"
}
}
}
@@ -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<IrDeclaration>? {
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<String>
val pathReplacement = stringLiteral.getReplacement() ?: return
jsModuleAnnotation.putValueArgument(0, pathReplacement)
}
private fun IrConst<String>.getReplacement(): IrConst<String>? {
val replacement = replacements[value] ?: replacements[value.replace("./", "")] ?: return null
return IrConstImpl.string(startOffset, endOffset, type, "./" + replacement.replace("./", ""))
}
private fun TestServices.collectReplacementsMap(): Map<String, String> {
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()
}
}
@@ -5,25 +5,15 @@
package org.jetbrains.kotlin.js.test.handlers 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.js.test.utils.*
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.services.TestServices 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.defaultsProvider
import org.jetbrains.kotlin.test.services.moduleStructure 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) { class JsBoxRunner(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) {
override fun processAfterAllModules(someAssertionWasFailed: Boolean) { override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
if (someAssertionWasFailed) return if (!someAssertionWasFailed) {
if (JsEnvironmentConfigurationDirectives.ES_MODULES in testServices.moduleStructure.allDirectives) {
runEsCode()
} else {
runJsCode() runJsCode()
} }
} }
@@ -43,37 +33,30 @@ class JsBoxRunner(testServices: TestServices) : AbstractJsArtifactsCollector(tes
val dontSkipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE !in globalDirectives val dontSkipRegularMode = JsEnvironmentConfigurationDirectives.SKIP_REGULAR_MODE !in globalDirectives
if (dontSkipRegularMode) { if (dontSkipRegularMode) {
for (jsFiles in allJsFiles.values) { for ((mode, jsFiles) in allJsFiles) {
runGeneratedCode(jsFiles, testModuleName, testPackage, withModuleSystem) val entryModulePath = extractEntryModulePath(mode, testServices)
runGeneratedCode(entryModulePath, jsFiles, testModuleName, testPackage, withModuleSystem)
} }
} }
} }
private fun runEsCode() { private fun runGeneratedCode(
val globalDirectives = testServices.moduleStructure.allDirectives entryModulePath: String?,
jsFiles: List<String>,
val esmOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices).esModulesSubDir testModuleName: String?,
val esmDceOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_DCE_MINIMIZED_NAMES).esModulesSubDir testPackage: String?,
withModuleSystem: Boolean
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<String>, testModuleName: String?, testPackage: String?, withModuleSystem: Boolean) {
getTestChecker(testServices) 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 { companion object {
@@ -24,6 +24,7 @@ class JsDtsHandler(testServices: TestServices) : JsBinaryArtifactHandler(testSer
val referenceDtsFile = module.files.first().originalFile.withReplacedExtensionOrNull(".kt", ".d.ts") val referenceDtsFile = module.files.first().originalFile.withReplacedExtensionOrNull(".kt", ".d.ts")
?: error("Can't find reference .d.ts file") ?: error("Can't find reference .d.ts file")
val generatedDtsFile = info.outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts") val generatedDtsFile = info.outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts")
?: info.outputFile.withReplacedExtensionOrNull("_v5.mjs", ".d.ts")
?: error("Can't find generated .d.ts file") ?: error("Can't find generated .d.ts file")
val generatedDts = generatedDtsFile.readText() val generatedDts = generatedDtsFile.readText()
@@ -10,9 +10,10 @@ import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.test.JsAdditionalSourceProvider import org.jetbrains.kotlin.js.test.JsAdditionalSourceProvider
import org.jetbrains.kotlin.js.test.converters.augmentWithModuleName 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.test.handlers.JsBoxRunner.Companion.TEST_FUNCTION
import org.jetbrains.kotlin.js.testOld.* import org.jetbrains.kotlin.js.testOld.*
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.TargetBackend 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" private const val MODULE_EMULATION_FILE = "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/moduleEmulation.js"
val File.esModulesSubDir: File fun TestModule.getNameFor(filePath: String, testServices: TestServices): String {
get() = File(absolutePath + "_esm") return JsEnvironmentConfigurator.getJsArtifactSimpleName(testServices, name) + "-js-" + filePath
}
private fun extractJsFiles(testServices: TestServices, modules: List<TestModule>): Pair<List<String>, List<String>> { fun TestModule.getNameFor(file: TestFile, testServices: TestServices): String {
val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices) return getNameFor(file.name, testServices)
}
private fun extractJsFiles(
testServices: TestServices,
modules: List<TestModule>,
mode: TranslationMode = TranslationMode.FULL,
): Pair<List<String>, List<String>> {
val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, mode)
fun copyInputJsFile(module: TestModule, inputJsFile: TestFile): String { 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) val targetFile = File(outputDir, newName)
targetFile.writeText(inputJsFile.originalContent) targetFile.writeText(inputJsFile.originalContent)
return targetFile.absolutePath return targetFile.absolutePath
@@ -45,49 +55,74 @@ private fun extractJsFiles(testServices: TestServices, modules: List<TestModule>
val inputJsFiles = modules val inputJsFiles = modules
.flatMap { module -> module.files.map { module to it } } .flatMap { module -> module.files.map { module to it } }
.filter { it.second.isJsFile } .filter { it.second.isJsFile || it.second.isMjsFile }
val after = inputJsFiles 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) } .map { (module, inputJsFile) -> copyInputJsFile(module, inputJsFile) }
val before = inputJsFiles 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) } .map { (module, inputJsFile) -> copyInputJsFile(module, inputJsFile) }
return before to after return before to after
} }
private fun getAdditionalFiles(testServices: TestServices): List<String> { fun getAdditionalFilePathes(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL): List<String> {
return getAdditionalFiles(testServices, mode, true).map { it.absolutePath }
}
fun getAdditionalFiles(
testServices: TestServices,
mode: TranslationMode = TranslationMode.FULL,
shouldCopyFiles: Boolean = false
): List<File> {
val originalFile = testServices.moduleStructure.originalTestDataFiles.first() val originalFile = testServices.moduleStructure.originalTestDataFiles.first()
val withModuleSystem = testWithModuleSystem(testServices) val withModuleSystem = testWithModuleSystem(testServices)
val additionalFiles = mutableListOf<String>() val additionalFiles = mutableListOf<File>()
if (withModuleSystem) additionalFiles += File(MODULE_EMULATION_FILE).absolutePath if (withModuleSystem) additionalFiles += File(MODULE_EMULATION_FILE)
originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_EXTENSION) originalFile.parentFile.resolve(originalFile.nameWithoutExtension + JavaScript.DOT_EXTENSION)
.takeIf { it.exists() } .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 return additionalFiles
} }
private fun getAdditionalMjsFiles(testServices: TestServices): List<String> { fun getAdditionalMainFilePathes(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL): List<String> {
val originalFile = testServices.moduleStructure.originalTestDataFiles.first() return getAdditionalMainFiles(testServices, mode, shouldCopyFiles = true).map { it.absolutePath }
return originalFile.parentFile.resolve(originalFile.nameWithoutExtension + ".mjs")
.takeIf { it.exists() }
?.let { listOf(it.absolutePath) } ?: emptyList()
} }
private fun getAdditionalMainFiles(testServices: TestServices): List<String> { fun getAdditionalMainFiles(
testServices: TestServices,
mode: TranslationMode = TranslationMode.FULL,
shouldCopyFiles: Boolean = false
): List<File> {
val originalFile = testServices.moduleStructure.originalTestDataFiles.first() val originalFile = testServices.moduleStructure.originalTestDataFiles.first()
val additionalFiles = mutableListOf<String>() val additionalFiles = mutableListOf<File>()
originalFile.parentFile.resolve(originalFile.nameWithoutExtension + "__main.js") originalFile.parentFile.resolve(originalFile.nameWithoutExtension + "__main.js")
.takeIf { it.exists() } .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 return additionalFiles
} }
@@ -99,26 +134,31 @@ fun testWithModuleSystem(testServices: TestServices): Boolean {
return mainModuleKind != ModuleKind.PLAIN && NO_JS_MODULE_SYSTEM !in globalDirectives 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( fun getAllFilesForRunner(
testServices: TestServices, modulesToArtifact: Map<TestModule, BinaryArtifacts.Js> testServices: TestServices, modulesToArtifact: Map<TestModule, BinaryArtifacts.Js>
): Map<TranslationMode, List<String>> { ): Map<TranslationMode, List<String>> {
val originalFile = testServices.moduleStructure.originalTestDataFiles.first() val originalFile = testServices.moduleStructure.originalTestDataFiles.first()
val commonFiles = JsAdditionalSourceProvider.getAdditionalJsFiles(originalFile.parent).map { it.absolutePath } 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 }) { if (modulesToArtifact.values.any { it is BinaryArtifacts.Js.JsIrArtifact }) {
// JS IR // 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<TranslationMode, List<String>>() val result = mutableMapOf<TranslationMode, List<String>>()
compilerResult.outputs.entries.forEach { (mode, outputs) -> compilerResult.outputs.entries.forEach { (mode, outputs) ->
val paths = mutableListOf<String>() val paths = mutableListOf<String>()
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, _) -> outputs.dependencies.forEach { (moduleId, _) ->
paths += outputFile.augmentWithModuleName(moduleId) paths += outputFile.augmentWithModuleName(moduleId)
} }
@@ -129,12 +169,15 @@ fun getAllFilesForRunner(
return result return result
} else { } 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 outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices)
val dceOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_DCE_MINIMIZED_NAMES) val dceOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_DCE_MINIMIZED_NAMES)
val artifactsPaths = modulesToArtifact.values.map { it.outputFile.absolutePath }.filter { !File(it).isDirectory } 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<TranslationMode, List<String>>() val result = mutableMapOf<TranslationMode, List<String>>()
@@ -154,38 +197,6 @@ fun getAllFilesForRunner(
} }
} }
fun extractAllFilesForEsRunner(testServices: TestServices, esmOutputDir: File): Pair<List<String>, List<String>> {
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<TestModule, BinaryArtifacts.Js>): List<String> { fun getOnlyJsFilesForRunner(testServices: TestServices, modulesToArtifact: Map<TestModule, BinaryArtifacts.Js>): List<String> {
return getAllFilesForRunner(testServices, modulesToArtifact).let { return getAllFilesForRunner(testServices, modulesToArtifact).let {
it[TranslationMode.FULL] ?: it[TranslationMode.PER_MODULE]!! it[TranslationMode.FULL] ?: it[TranslationMode.PER_MODULE]!!
@@ -215,8 +226,49 @@ fun getBoxFunction(testServices: TestServices): KtNamedFunction? {
}.singleOrNull() }.singleOrNull()
} }
fun extractTestPackage(testServices: TestServices): String? = fun extractTestPackage(testServices: TestServices, ignoreEsModules: Boolean = true): String? {
getBoxFunction(testServices)?.containingKtFile?.packageFqName?.asString()?.takeIf { it.isNotEmpty() } 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 { fun getTestChecker(testServices: TestServices): AbstractJsTestChecker {
val runTestInNashorn = java.lang.Boolean.getBoolean("kotlin.js.useNashorn") val runTestInNashorn = java.lang.Boolean.getBoolean("kotlin.js.useNashorn")
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.js.engine.loadFiles
import org.junit.Assert import org.junit.Assert
private const val DIST_DIR_JS_PATH = "dist/js/" private const val DIST_DIR_JS_PATH = "dist/js/"
private const val ESM_EXTENSION = ".mjs"
fun createScriptEngine(): ScriptEngine { fun createScriptEngine(): ScriptEngine {
return if (java.lang.Boolean.getBoolean("kotlin.js.useNashorn")) ScriptEngineNashorn() else ScriptEngineV8() 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);") 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( fun ScriptEngine.runTestFunction(
testModuleName: String?, testModuleName: String?,
testPackageName: String?, testPackageName: String?,
testFunctionName: String, testFunctionName: String,
withModuleSystem: Boolean, withModuleSystem: Boolean,
testFunctionArgs: String = "", testFunctionArgs: String = "",
entryModulePath: String? = null,
): String { ): 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 { 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!! + "')" withModuleSystem -> "\$kotlin_test_internal\$.require('" + testModuleName!! + "')"
testModuleName === null -> "this" testModuleName === null -> "this"
else -> testModuleName else -> testModuleName
@@ -51,9 +64,10 @@ abstract class AbstractJsTestChecker {
testPackageName: String?, testPackageName: String?,
testFunctionName: String, testFunctionName: String,
expectedResult: 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()) Assert.assertEquals(expectedResult, actualResult.normalize())
} }
@@ -76,9 +90,10 @@ abstract class AbstractJsTestChecker {
testPackageName: String?, testPackageName: String?,
testFunctionName: String, testFunctionName: String,
testFunctionArgs: String, testFunctionArgs: String,
withModuleSystem: Boolean withModuleSystem: Boolean,
entryModulePath: String? = null,
) = run(files) { ) = run(files) {
runTestFunction(testModuleName, testPackageName, testFunctionName, withModuleSystem, testFunctionArgs) runTestFunction(testModuleName, testPackageName, testFunctionName, withModuleSystem, testFunctionArgs, entryModulePath)
} }
@@ -1906,18 +1906,6 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
public void testAllFilesPresentInExport() throws Exception { 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); 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 @Nested
@@ -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); 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 @Test
@TestMetadata("exportAllFile.kt") @TestMetadata("exportAllFile.kt")
public void testExportAllFile() throws Exception { public void testExportAllFile() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/exportAllFile.kt"); 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 @Test
@TestMetadata("nonIndetifierModuleName.kt") @TestMetadata("nonIndetifierModuleName.kt")
public void testNonIndetifierModuleName() throws Exception { public void testNonIndetifierModuleName() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt"); 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 @Test
@TestMetadata("overriddenChainNonExportIntermediate.kt") @TestMetadata("overriddenChainNonExportIntermediate.kt")
public void testOverriddenChainNonExportIntermediate() throws Exception { public void testOverriddenChainNonExportIntermediate() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt"); 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 @Test
@TestMetadata("overriddenExternalMethodWithSameNameMethod.kt") @TestMetadata("overriddenExternalMethodWithSameNameMethod.kt")
public void testOverriddenExternalMethodWithSameNameMethod() throws Exception { public void testOverriddenExternalMethodWithSameNameMethod() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt"); 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 @Test
@TestMetadata("reservedModuleName.kt") @TestMetadata("reservedModuleName.kt")
public void testReservedModuleName() throws Exception { public void testReservedModuleName() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/reservedModuleName.kt"); 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 @Nested
@@ -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); 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 @Test
@TestMetadata("exportAllFile.kt") @TestMetadata("exportAllFile.kt")
public void testExportAllFile() throws Exception { public void testExportAllFile() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/exportAllFile.kt"); 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 @Test
@TestMetadata("nonIndetifierModuleName.kt") @TestMetadata("nonIndetifierModuleName.kt")
public void testNonIndetifierModuleName() throws Exception { public void testNonIndetifierModuleName() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/nonIndetifierModuleName.kt"); 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 @Test
@TestMetadata("overriddenChainNonExportIntermediate.kt") @TestMetadata("overriddenChainNonExportIntermediate.kt")
public void testOverriddenChainNonExportIntermediate() throws Exception { public void testOverriddenChainNonExportIntermediate() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/overriddenChainNonExportIntermediate.kt"); 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 @Test
@TestMetadata("overriddenExternalMethodWithSameNameMethod.kt") @TestMetadata("overriddenExternalMethodWithSameNameMethod.kt")
public void testOverriddenExternalMethodWithSameNameMethod() throws Exception { public void testOverriddenExternalMethodWithSameNameMethod() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/overriddenExternalMethodWithSameNameMethod.kt"); 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 @Test
@TestMetadata("reservedModuleName.kt") @TestMetadata("reservedModuleName.kt")
public void testReservedModuleName() throws Exception { public void testReservedModuleName() throws Exception {
runTest("js/js.translator/testData/box/esModules/export/reservedModuleName.kt"); 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 @Nested
@@ -456,6 +456,12 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp
runTest("js/js.translator/testData/typescript-export/module-systems/commonjs.kt"); 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 @Test
@TestMetadata("plain.kt") @TestMetadata("plain.kt")
public void testPlain() throws Exception { 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"); 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 @Test
@TestMetadata("plain.kt") @TestMetadata("plain.kt")
public void testPlain() throws Exception { public void testPlain() throws Exception {
@@ -0,0 +1,31 @@
// DONT_TARGET_EXACT_BACKEND: JS
// ES_MODULES
// MODULE: bridge_saving_after_export
// FILE: lib.kt
@JsExport
open class A<T> {
open fun foo(value: T): T = value
}
@JsExport
class B: A<String>() {
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"
}
@@ -0,0 +1,30 @@
// DONT_TARGET_EXACT_BACKEND: JS
// ES_MODULES
// MODULE: bridge_saving_after_export
// FILE: lib.kt
@file:JsExport
open class A<T> {
open fun foo(value: T): T = value
}
class B: A<String>() {
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"
}
@@ -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;
}
@@ -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;
}
@@ -1,6 +1,5 @@
// DONT_TARGET_EXACT_BACKEND: JS // DONT_TARGET_EXACT_BACKEND: JS
// EXPECTED_REACHABLE_NODES: 1252 // EXPECTED_REACHABLE_NODES: 1252
// INFER_MAIN_MODULE
// ES_MODULES // ES_MODULES
// MODULE: export_all_file // MODULE: export_all_file
@@ -19,5 +18,9 @@ class B : A() {
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // ENTRY_ES_MODULE
import { B } from "./export_all_file/index.js";
console.assert(new B().foo("K") == "OK"); import { B } from "./exportAllFile-export_all_file_v5.mjs";
export function box() {
return new B().foo("K")
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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';
}
@@ -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';
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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';
}
@@ -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';
}
@@ -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"
}
@@ -1,10 +1,9 @@
// DONT_TARGET_EXACT_BACKEND: JS // DONT_TARGET_EXACT_BACKEND: JS
// SKIP_MINIFICATION // SKIP_MINIFICATION
// INFER_MAIN_MODULE
// SKIP_NODE_JS // SKIP_NODE_JS
// ES_MODULES // ES_MODULES
// MODULE: non_identifier_module_name // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
@JsName("foo") @JsName("foo")
@JsExport @JsExport
@@ -12,5 +11,8 @@ public fun foo(k: String): String = "O$k"
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // ENTRY_ES_MODULE
import { foo } from "./non_identifier_module_name/index.js"; import { foo } from "./nonIndetifierModuleName-lib_v5.mjs";
console.assert(foo("K") == "OK");
export function box() {
return foo("K")
}
@@ -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")
}
@@ -1,6 +1,5 @@
// DONT_TARGET_EXACT_BACKEND: JS // DONT_TARGET_EXACT_BACKEND: JS
// EXPECTED_REACHABLE_NODES: 1252 // EXPECTED_REACHABLE_NODES: 1252
// INFER_MAIN_MODULE
// ES_MODULES // ES_MODULES
// MODULE: overriden_chain_non_export_intermediate // MODULE: overriden_chain_non_export_intermediate
@@ -28,12 +27,19 @@ class C : B() {
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // 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) { export function box() {
if (c.foo() === "foo" && c.bar() === "bar" && c.bay() == "bay") return "OK" 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}`
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"
} }
console.assert(test(new C()) == "OK");
@@ -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"
}
@@ -1,9 +1,8 @@
// EXPECTED_REACHABLE_NODES: 1252 // EXPECTED_REACHABLE_NODES: 1252
// INFER_MAIN_MODULE
// DONT_TARGET_EXACT_BACKEND: JS // DONT_TARGET_EXACT_BACKEND: JS
// ES_MODULES // ES_MODULES
// MODULE: overriden_external_method_with_same_name_method // MODULE: lib
// FILE: lib.kt // FILE: lib.kt
external abstract class Foo { external abstract class Foo {
abstract fun o(): String abstract fun o(): String
@@ -32,10 +31,9 @@ Foo.prototype.k = function() {
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // 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() return foo.o() + foo.k()
} }
console.assert(test(new Baz()) == "OK");
@@ -1,12 +1,8 @@
// EXPECTED_REACHABLE_NODES: 1252 // EXPECTED_REACHABLE_NODES: 1252
// IGNORE_BACKEND: JS // DONT_TARGET_EXACT_BACKEND: JS
// INFER_MAIN_MODULE
// ES_MODULES // ES_MODULES
// TODO: Fix tests on Windows // MODULE: lib
// DONT_TARGET_EXACT_BACKEND: JS_IR
// MODULE: overriden_external_method_with_same_stable_name_method
// FILE: lib.kt // FILE: lib.kt
external abstract class Foo { external abstract class Foo {
abstract fun o(): String abstract fun o(): String
@@ -36,12 +32,11 @@ Foo.prototype.k = function() {
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // 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") const oStable = foo.oStable("OK")
if (oStable !== "OK") return "false: " + oStable if (oStable !== "OK") return "false: " + oStable
return foo.o() + foo.k() return foo.o() + foo.k()
} }
console.assert(test(new Baz()) == "OK");
@@ -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()
}
@@ -1,7 +1,6 @@
// IGNORE_BACKEND: JS // DONT_TARGET_EXACT_BACKEND: JS
// EXPECTED_REACHABLE_NODES: 1270 // EXPECTED_REACHABLE_NODES: 1270
// SKIP_MINIFICATION // SKIP_MINIFICATION
// INFER_MAIN_MODULE
// ES_MODULES // ES_MODULES
// MODULE: if // MODULE: if
@@ -12,6 +11,8 @@ public fun foo(k: String): String = "O$k"
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // 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")
}
@@ -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")
}
@@ -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)])
}
@@ -30,6 +30,8 @@ fun testOk(ok: Any): String {
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // 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())
}
@@ -48,8 +48,5 @@ fun box(): String {
return "Fail6: ${res.component2}" return "Fail6: ${res.component2}"
} }
return "OK" return "OK"
} }
@@ -1,4 +1,4 @@
import { Point } from "./main/index.js"; import { Point } from "./dataClass_v5.mjs";
export default function() { export default function() {
var p = new Point(3, 7); var p = new Point(3, 7);
@@ -1,4 +1,4 @@
import * as api from "./main/index.js"; import * as api from "./exportedDefaultStub_v5.mjs";
export default function() { export default function() {
var ping = api.ping; var ping = api.ping;
@@ -1,4 +1,4 @@
import { A, B } from "./main/index.js"; import { A, B } from "./jsExportInClass_v5.mjs";
export default function() { export default function() {
return { return {
@@ -1,4 +1,4 @@
import { ping, Something } from "./main/index.js" import { ping, Something } from "./recursiveExport_v5.mjs"
export default function() { export default function() {
return { return {
@@ -24,7 +24,7 @@ open class B {
// FILE: entry.mjs // FILE: entry.mjs
// ENTRY_ES_MODULE // ENTRY_ES_MODULE
import { A, B } from "./main/index.js"; import { A, B } from "./inheritanceInNativeClass_v5.mjs";
function createA() { function createA() {
function ADerived() { function ADerived() {
@@ -46,7 +46,7 @@ function createB() {
return new BDerived(); return new BDerived();
} }
function box() { export function box() {
let a = createA(); let a = createA();
if (a.bar(0) != 124) return "fail1"; if (a.bar(0) != 124) return "fail1";
@@ -55,5 +55,3 @@ function box() {
return "OK"; return "OK";
} }
console.assert(box() == "OK");
@@ -1,5 +1,5 @@
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
export namespace foo { export declare namespace foo {
const prop: number; const prop: number;
class C { class C {
constructor(x: number); constructor(x: number);
@@ -0,0 +1,33 @@
type Nullable<T> = 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;
};
}
@@ -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"
@@ -1,5 +1,5 @@
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
export namespace foo { export declare namespace foo {
const prop: number; const prop: number;
class C { class C {
constructor(x: number); constructor(x: number);
@@ -1,5 +1,5 @@
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
export namespace foo { export declare namespace foo {
const prop: number; const prop: number;
class C { class C {
constructor(x: number); constructor(x: number);
@@ -0,0 +1,33 @@
type Nullable<T> = 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;
};
}
@@ -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"
@@ -1,5 +1,5 @@
type Nullable<T> = T | null | undefined type Nullable<T> = T | null | undefined
export namespace foo { export declare namespace foo {
const prop: number; const prop: number;
class C { class C {
constructor(x: number); constructor(x: number);
@@ -8,6 +8,12 @@ declare namespace JS_TESTS {
foo(): number; foo(): number;
}; };
function takesO(o: typeof foo.O): number; function takesO(o: typeof foo.O): number;
const WithSimpleObjectInside: {
get value(): string;
get SimpleObject(): {
get value(): string;
};
};
abstract class Parent { abstract class Parent {
private constructor(); private constructor();
} }
@@ -26,6 +26,14 @@ fun takesO(o: O): Int =
O.x + O.foo() O.x + O.foo()
object WithSimpleObjectInside {
val value: String = "WithSimpleObjectInside"
object SimpleObject {
val value: String = "SimpleObject"
}
}
object Parent { object Parent {
object Nested1 { object Nested1 {
val value: String = "Nested1" val value: String = "Nested1"
@@ -7,6 +7,7 @@ var getParent = JS_TESTS.foo.getParent;
var createNested1 = JS_TESTS.foo.createNested1; var createNested1 = JS_TESTS.foo.createNested1;
var createNested2 = JS_TESTS.foo.createNested2; var createNested2 = JS_TESTS.foo.createNested2;
var createNested3 = JS_TESTS.foo.createNested3; var createNested3 = JS_TESTS.foo.createNested3;
var WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside;
function assert(condition) { function assert(condition) {
if (!condition) { if (!condition) {
throw "Assertion failed"; throw "Assertion failed";
@@ -27,5 +28,7 @@ function box() {
assert(createNested1() === nested1); assert(createNested1() === nested1);
assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2); assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2);
assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3); assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3);
assert(WithSimpleObjectInside.value === "WithSimpleObjectInside");
assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject");
return "OK"; return "OK";
} }
@@ -6,6 +6,7 @@ import getParent = JS_TESTS.foo.getParent;
import createNested1 = JS_TESTS.foo.createNested1; import createNested1 = JS_TESTS.foo.createNested1;
import createNested2 = JS_TESTS.foo.createNested2; import createNested2 = JS_TESTS.foo.createNested2;
import createNested3 = JS_TESTS.foo.createNested3; import createNested3 = JS_TESTS.foo.createNested3;
import WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside;
function assert(condition: boolean) { function assert(condition: boolean) {
if (!condition) { if (!condition) {
@@ -30,5 +31,8 @@ function box(): string {
assert(createNested1() === nested1) assert(createNested1() === nested1)
assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2) assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2)
assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3) assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3)
assert(WithSimpleObjectInside.value === "WithSimpleObjectInside");
assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject");
return "OK"; return "OK";
} }
@@ -8,6 +8,12 @@ declare namespace JS_TESTS {
foo(): number; foo(): number;
}; };
function takesO(o: typeof foo.O): number; function takesO(o: typeof foo.O): number;
const WithSimpleObjectInside: {
get value(): string;
get SimpleObject(): {
get value(): string;
};
};
abstract class Parent { abstract class Parent {
private constructor(); private constructor();
} }
@@ -21,6 +21,14 @@ object O {
fun takesO(o: O): Int = fun takesO(o: O): Int =
O.x + O.foo() O.x + O.foo()
@JsExport
object WithSimpleObjectInside {
val value: String = "WithSimpleObjectInside"
object SimpleObject {
val value: String = "SimpleObject"
}
}
@JsExport @JsExport
object Parent { object Parent {
object Nested1 { object Nested1 {
@@ -7,6 +7,7 @@ var getParent = JS_TESTS.foo.getParent;
var createNested1 = JS_TESTS.foo.createNested1; var createNested1 = JS_TESTS.foo.createNested1;
var createNested2 = JS_TESTS.foo.createNested2; var createNested2 = JS_TESTS.foo.createNested2;
var createNested3 = JS_TESTS.foo.createNested3; var createNested3 = JS_TESTS.foo.createNested3;
var WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside;
function assert(condition) { function assert(condition) {
if (!condition) { if (!condition) {
throw "Assertion failed"; throw "Assertion failed";
@@ -27,5 +28,7 @@ function box() {
assert(createNested1() === nested1); assert(createNested1() === nested1);
assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2); assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2);
assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3); assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3);
assert(WithSimpleObjectInside.value === "WithSimpleObjectInside");
assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject");
return "OK"; return "OK";
} }
@@ -6,6 +6,7 @@ import getParent = JS_TESTS.foo.getParent;
import createNested1 = JS_TESTS.foo.createNested1; import createNested1 = JS_TESTS.foo.createNested1;
import createNested2 = JS_TESTS.foo.createNested2; import createNested2 = JS_TESTS.foo.createNested2;
import createNested3 = JS_TESTS.foo.createNested3; import createNested3 = JS_TESTS.foo.createNested3;
import WithSimpleObjectInside = JS_TESTS.foo.WithSimpleObjectInside;
function assert(condition: boolean) { function assert(condition: boolean) {
if (!condition) { if (!condition) {
@@ -30,5 +31,8 @@ function box(): string {
assert(createNested1() === nested1) assert(createNested1() === nested1)
assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2) assert(createNested2() !== nested2 && createNested2() instanceof Parent.Nested1.Nested2)
assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3) assert(createNested3() !== nested3 && createNested3() instanceof Parent.Nested1.Nested2.Companion.Nested3)
assert(WithSimpleObjectInside.value === "WithSimpleObjectInside");
assert(WithSimpleObjectInside.SimpleObject.value === "SimpleObject");
return "OK"; return "OK";
} }
@@ -183,4 +183,8 @@ constructor(
} }
} }
} }
override fun useEsModules() {
error("ES modules are not supported in legacy JS compiler. Please, use IR one instead.")
}
} }
@@ -53,6 +53,7 @@ interface KotlinJsTargetDsl : KotlinTarget {
} }
fun useCommonJs() fun useCommonJs()
fun useEsModules()
val binaries: KotlinJsBinaryContainer val binaries: KotlinJsBinaryContainer
@@ -358,9 +358,30 @@ constructor(
legacyTarget?.useCommonJs() 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() { private fun KotlinJsOptions.configureCommonJsOptions() {
moduleKind = "commonjs" moduleKind = "commonjs"
sourceMap = true sourceMap = true
sourceMapEmbedSources = "never" sourceMapEmbedSources = "never"
} }
private fun KotlinJsOptions.configureEsModulesOptions() {
moduleKind = "es"
sourceMap = true
sourceMapEmbedSources = "never"
}
} }