[Wasm] Support JsModule and JsQualifier

This commit is contained in:
Svyatoslav Kuzmich
2023-01-10 21:49:51 +01:00
committed by Space Team
parent 75d3ae4466
commit d14d4c8510
18 changed files with 669 additions and 31 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.wasm
import org.jetbrains.kotlin.backend.common.ir.Ir
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.wasm.ir2wasm.JsModuleAndQualifierReference
import org.jetbrains.kotlin.backend.wasm.lower.WasmSharedVariablesManager
import org.jetbrains.kotlin.backend.wasm.utils.WasmInlineClassesUtils
import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -23,7 +24,6 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
@@ -63,6 +63,9 @@ class WasmBackendContext(
val jsClosureCallers = mutableMapOf<IrSimpleType, IrSimpleFunction>()
val jsToKotlinClosures = mutableMapOf<IrSimpleType, IrSimpleFunction>()
val jsModuleAndQualifierReferences =
mutableSetOf<JsModuleAndQualifierReference>()
override val coroutineSymbols =
JsCommonCoroutineSymbols(symbolTable, module,this)
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.wasm
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.checkNoUnboundSymbols
import org.jetbrains.kotlin.backend.wasm.ir2wasm.JsModuleAndQualifierReference
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
import org.jetbrains.kotlin.backend.wasm.ir2wasm.toJsStringLiteral
@@ -107,7 +108,10 @@ fun compileWasm(
null
}
val jsUninstantiatedWrapper = compiledWasmModule.generateAsyncJsWrapper("./$baseFileName.wasm")
val jsUninstantiatedWrapper = compiledWasmModule.generateAsyncJsWrapper(
"./$baseFileName.wasm",
backendContext.jsModuleAndQualifierReferences
)
val jsWrapper = generateEsmExportsWrapper("./$baseFileName.uninstantiated.mjs")
val os = ByteArrayOutputStream()
@@ -172,43 +176,74 @@ private fun generateSourceMap(
return sourceMapBuilder.build()
}
fun WasmCompiledModuleFragment.generateAsyncJsWrapper(wasmFilePath: String): String {
fun WasmCompiledModuleFragment.generateAsyncJsWrapper(
wasmFilePath: String,
jsModuleAndQualifierReferences: Set<JsModuleAndQualifierReference>
): String {
val jsCodeBody = jsFuns.joinToString(",\n") {
"${it.importName.toJsStringLiteral()} : ${it.jsCode}"
}
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
val jsCodeBodyIndented = jsCodeBody.prependIndent(" ")
val imports = jsModuleImports
.toList()
.sorted()
.joinToString("") {
val moduleSpecifier = it.toJsStringLiteral()
" $moduleSpecifier: imports[$moduleSpecifier] ?? await import($moduleSpecifier),\n"
" $moduleSpecifier: await _importModule($moduleSpecifier),\n"
}
val referencesToQualifiedAndImportedDeclarations = jsModuleAndQualifierReferences
.map {
val module = it.module
val qualifier = it.qualifier
buildString {
append(" const ")
append(it.jsVariableName)
append(" = ")
if (module != null) {
append("(await _importModule(${module.toJsStringLiteral()}))")
if (qualifier != null)
append(".")
}
if (qualifier != null) {
append(qualifier)
}
append(";")
}
}.sorted()
.joinToString("\n")
//language=js
return """
const externrefBoxes = new WeakMap();
// ref must be non-null
function tryGetOrSetExternrefBox(ref, ifNotCached) {
if (typeof ref !== 'object') return ifNotCached;
const cachedBox = externrefBoxes.get(ref);
if (cachedBox !== void 0) return cachedBox;
externrefBoxes.set(ref, ifNotCached);
return ifNotCached;
}
const js_code = {
$jsCodeBodyIndented
}
// Placed here to give access to it from externals (js_code)
let wasmInstance;
let require;
let wasmExports;
export async function instantiate(imports={}, runInitializer=true) {
const externrefBoxes = new WeakMap();
// ref must be non-null
function tryGetOrSetExternrefBox(ref, ifNotCached) {
if (typeof ref !== 'object') return ifNotCached;
const cachedBox = externrefBoxes.get(ref);
if (cachedBox !== void 0) return cachedBox;
externrefBoxes.set(ref, ifNotCached);
return ifNotCached;
}
async function _importModule(x) {
return imports[x] ?? await import(x);
}
$referencesToQualifiedAndImportedDeclarations
const js_code = {
$jsCodeBodyIndented
}
// Placed here to give access to it from externals (js_code)
let wasmInstance;
let require;
let wasmExports;
const isNodeJs = (typeof process !== 'undefined') && (process.release.name === 'node');
const isD8 = !isNodeJs && (typeof d8 !== 'undefined');
const isBrowser = !isNodeJs && !isD8 && (typeof window !== 'undefined');
@@ -6,6 +6,20 @@
package org.jetbrains.kotlin.backend.wasm.ir2wasm
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
import java.util.Base64
fun String.toJsStringLiteral(): CharSequence =
JsToStringGenerationVisitor.javaScriptString(this)
JsToStringGenerationVisitor.javaScriptString(this)
data class JsModuleAndQualifierReference(
val module: String?,
val qualifier: String?,
) {
val jsVariableName = run {
// Encode variable name as base64 to have a valid unique JS identifier
val encoder = Base64.getEncoder().withoutPadding()
val moduleBase64 = module?.let { encoder.encodeToString(module.encodeToByteArray()) }.orEmpty()
val qualifierBase64 = qualifier?.let { encoder.encodeToString(qualifier.encodeToByteArray()) }.orEmpty()
"_ref_${moduleBase64}_$qualifierBase64"
}
}
@@ -8,12 +8,15 @@ package org.jetbrains.kotlin.backend.wasm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.ir2wasm.JsModuleAndQualifierReference
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.backend.wasm.utils.getJsFunAnnotation
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.utils.getJsModule
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.getJsQualifier
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
@@ -108,7 +111,7 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
val dispatchReceiver = getter.dispatchReceiverParameter
val jsCode =
if (dispatchReceiver == null)
"() => $propName"
"() => ${referenceTopLevelExternalDeclaration(property)}"
else
"(_this) => _this.$propName"
@@ -130,7 +133,7 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
val dispatchReceiver = setter.dispatchReceiverParameter
val jsCode =
if (dispatchReceiver == null)
"(v) => $propName = v"
"(v) => ${referenceTopLevelExternalDeclaration(property)} = v"
else
"(_this, v) => _this.$propName = v"
@@ -159,12 +162,19 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
return
}
append('.')
append(klass.getJsNameOrKotlinName())
} else {
append(referenceTopLevelExternalDeclaration(klass))
}
append(klass.getJsNameOrKotlinName())
}
fun processExternalConstructor(constructor: IrConstructor) {
val klass = constructor.constructedClass
// External interfaces can have synthetic primary constructors in K/JS
if (klass.isInterface)
return
processFunctionOrConstructor(
function = constructor,
name = klass.name,
@@ -183,14 +193,26 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
val jsFun = function.getJsFunAnnotation()
// Wrap external functions without @JsFun to lambdas `foo` -> `(a, b) => foo(a, b)`.
// This way we wouldn't fail if we don't call them.
if (jsFun != null && function.valueParameters.all { it.defaultValue == null && it.varargElementType == null })
if (jsFun != null &&
function.valueParameters.all { it.defaultValue == null && it.varargElementType == null } &&
currentFile.getJsQualifier() == null &&
currentFile.getJsModule() == null
) {
return
}
val jsFunctionReference = when {
jsFun != null -> "($jsFun)"
function.isTopLevelDeclaration -> referenceTopLevelExternalDeclaration(function)
else -> function.getJsNameOrKotlinName().identifier
}
processFunctionOrConstructor(
function = function,
name = function.name,
returnType = function.returnType,
isConstructor = false,
jsFunctionReference = jsFun?.let { "($it)" } ?: function.getJsNameOrKotlinName().identifier
jsFunctionReference = jsFunctionReference
)
}
@@ -341,6 +363,25 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
addedDeclarations += res
return res
}
private fun referenceTopLevelExternalDeclaration(declaration: IrDeclarationWithName): String {
var name = declaration.getJsNameOrKotlinName().identifier
val qualifier = currentFile.getJsQualifier()
val module = currentFile.getJsModule()
?: declaration.getJsModule()?.also {
// JsModule on top level declarations imports "default"
name = "default"
}
if (qualifier == null && module == null)
return name
val qualifieReference = JsModuleAndQualifierReference(module, qualifier)
context.jsModuleAndQualifierReferences += qualifieReference
return qualifieReference.jsVariableName + "." + name
}
}
/**
+52
View File
@@ -0,0 +1,52 @@
// ES_MODULES
// FILE: jsModule.mjs
let x = 10;
function f() { return 10; };
class C {
constructor(x) {
this.x = x;
}
}
export { x, f, C };
export default { defaultX: x, defaultF: f, defaultC: C };
// FILE: lib1.kt
@file:JsModule("./jsModule.mjs")
package named
external val x: Int
external fun f(): Int
external class C {
constructor(x: String)
val x: String
}
// FILE: lib2.kt
package default
@JsModule("./jsModule.mjs")
external object jsModule {
val defaultX: Int
fun defaultF(): Int
class defaultC {
constructor(x: String)
val x: String
}
}
// FILE: main.kt
fun box(): String {
if (named.x != 10) return "Fail1"
if (named.f() != 10) return "Fail2"
if (named.C("10").x != "10") return "Fail3"
if (default.jsModule.defaultX != 10) return "Fail4"
if (default.jsModule.defaultF() != 10) return "Fail5"
if (default.jsModule.defaultC("10").x != "10") return "Fail6"
return "OK"
}
@@ -0,0 +1,102 @@
// ES_MODULES
// FILE: jsModuleWithQualifier.mjs
let a = {
b: {
c: {
d: {
x: 10,
f() { return 10; },
C: class C {
constructor(x) {
this.x = x;
}
}
}
}
}
};
export { a };
// FILE: lib1.kt
@file:JsQualifier("a.b.c.d")
@file:JsModule("./jsModuleWithQualifier.mjs")
package abcd
external var x: Int
external fun f(): Int
external class C {
constructor(x: String)
val x: String
}
@JsName("x")
external var x2: Int
@JsName("f")
external fun f2(): Int
@JsName("C")
external class C2 {
constructor(x: String)
@JsName("x")
val x2: String
}
// FILE: lib2.kt
@file:JsQualifier("a")
@file:JsModule("./jsModuleWithQualifier.mjs")
package a
external object b {
class c {
companion object {
@JsName("d")
object d2 {
var x: Int
fun f(): Int
class C {
constructor(x: String)
val x: String
}
@JsName("x")
var x2: Int
@JsName("f")
fun f2(): Int
@JsName("C")
class C2 {
constructor(x: String)
@JsName("x")
val x2: String
}
}
}
}
}
// FILE: main.kt
fun box(): String {
if (abcd.x != 10) return "Fail1"
if (abcd.f() != 10) return "Fail2"
if (abcd.C("10").x != "10") return "Fail3"
if (abcd.x2 != 10) return "Fail4"
if (abcd.f2() != 10) return "Fail5"
if (abcd.C2("10").x2 != "10") return "Fail6"
if (a.b.c.Companion.d2.x != 10) return "Fail7"
if (a.b.c.Companion.d2.f() != 10) return "Fail8"
if (a.b.c.Companion.d2.C("10").x != "10") return "Fail9"
if (a.b.c.Companion.d2.x2 != 10) return "Fail10"
if (a.b.c.Companion.d2.f2() != 10) return "Fail11"
if (a.b.c.Companion.d2.C2("10").x2 != "10") return "Fail12"
return "OK"
}
@@ -0,0 +1,95 @@
// FILE: jsQualifier.js
let a = {
b: {
c: {
d: {
x: 10,
f() { return 10; },
C: class C {
constructor(x) {
this.x = x;
}
}
}
}
}
};
// FILE: lib1.kt
@file:JsQualifier("a.b.c.d")
package abcd
external var x: Int
external fun f(): Int
external class C {
constructor(x: String)
val x: String
}
@JsName("x")
external var x2: Int
@JsName("f")
external fun f2(): Int
@JsName("C")
external class C2 {
constructor(x: String)
@JsName("x")
val x2: String
}
// FILE: lib2.kt
@file:JsQualifier("a")
package a
external object b {
class c {
companion object {
@JsName("d")
object d2 {
var x: Int
fun f(): Int
class C {
constructor(x: String)
val x: String
}
@JsName("x")
var x2: Int
@JsName("f")
fun f2(): Int
@JsName("C")
class C2 {
constructor(x: String)
@JsName("x")
val x2: String
}
}
}
}
}
// FILE: main.kt
fun box(): String {
if (abcd.x != 10) return "Fail1"
if (abcd.f() != 10) return "Fail2"
if (abcd.C("10").x != "10") return "Fail3"
if (abcd.x2 != 10) return "Fail4"
if (abcd.f2() != 10) return "Fail5"
if (abcd.C2("10").x2 != "10") return "Fail6"
if (a.b.c.Companion.d2.x != 10) return "Fail7"
if (a.b.c.Companion.d2.f() != 10) return "Fail8"
if (a.b.c.Companion.d2.C("10").x != "10") return "Fail9"
if (a.b.c.Companion.d2.x2 != 10) return "Fail10"
if (a.b.c.Companion.d2.f2() != 10) return "Fail11"
if (a.b.c.Companion.d2.C2("10").x2 != "10") return "Fail12"
return "OK"
}
@@ -34,6 +34,15 @@ fun main(args: Array<String>) {
testClass<AbstractJsTranslatorWasmTest> {
model("box/main", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.WASM)
model("box/native/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.WASM)
model("box/esModules/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.WASM,
excludeDirs = listOf(
// JsExport is not supported for classes
"jsExport", "native", "export",
// Multimodal infra is not supported. Also, we don't use ES modules for cross-module refs in Wasm
"crossModuleRef", "crossModuleRefPerFile", "crossModuleRefPerModule"
)
)
model("box/jsQualifier/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.WASM)
}
testClass<AbstractJsTranslatorUnitWasmTest> {
@@ -55,6 +55,24 @@ public class FirJsCodegenWasmJsInteropTestGenerated extends AbstractFirJsCodegen
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
}
@Test
@TestMetadata("jsModule.kt")
public void testJsModule() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsModule.kt");
}
@Test
@TestMetadata("jsModuleWithQualifier.kt")
public void testJsModuleWithQualifier() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsModuleWithQualifier.kt");
}
@Test
@TestMetadata("jsQualifier.kt")
public void testJsQualifier() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsQualifier.kt");
}
@Test
@TestMetadata("jsToKotlinAdapters.kt")
public void testJsToKotlinAdapters() throws Exception {
@@ -55,6 +55,24 @@ public class IrCodegenWasmJsInteropJsTestGenerated extends AbstractIrCodegenWasm
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
}
@Test
@TestMetadata("jsModule.kt")
public void testJsModule() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsModule.kt");
}
@Test
@TestMetadata("jsModuleWithQualifier.kt")
public void testJsModuleWithQualifier() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsModuleWithQualifier.kt");
}
@Test
@TestMetadata("jsQualifier.kt")
public void testJsQualifier() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsQualifier.kt");
}
@Test
@TestMetadata("jsToKotlinAdapters.kt")
public void testJsToKotlinAdapters() throws Exception {
@@ -70,6 +70,21 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
}
@TestMetadata("jsModule.kt")
public void testJsModule() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsModule.kt");
}
@TestMetadata("jsModuleWithQualifier.kt")
public void testJsModuleWithQualifier() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsModuleWithQualifier.kt");
}
@TestMetadata("jsQualifier.kt")
public void testJsQualifier() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsQualifier.kt");
}
@TestMetadata("jsToKotlinAdapters.kt")
public void testJsToKotlinAdapters() throws Exception {
runTest("compiler/testData/codegen/boxWasmJsInterop/jsToKotlinAdapters.kt");
@@ -205,4 +205,162 @@ public class JsTranslatorWasmTestGenerated extends AbstractJsTranslatorWasmTest
runTest("js/js.translator/testData/box/native/vararg.kt");
}
}
@TestMetadata("js/js.translator/testData/box/esModules")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EsModules extends AbstractJsTranslatorWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInEsModules() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true, "jsExport", "native", "export", "crossModuleRef", "crossModuleRefPerFile", "crossModuleRefPerModule");
}
@TestMetadata("js/js.translator/testData/box/esModules/incremental")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Incremental extends AbstractJsTranslatorWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInIncremental() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/incremental"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
@TestMetadata("jsModule.kt")
public void testJsModule() throws Exception {
runTest("js/js.translator/testData/box/esModules/incremental/jsModule.kt");
}
}
@TestMetadata("js/js.translator/testData/box/esModules/inline")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Inline extends AbstractJsTranslatorWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInInline() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/inline"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
}
@TestMetadata("js/js.translator/testData/box/esModules/jsModule")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JsModule extends AbstractJsTranslatorWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInJsModule() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/jsModule"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
@TestMetadata("externalClass.kt")
public void testExternalClass() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalClass.kt");
}
@TestMetadata("externalClassNameClash.kt")
public void testExternalClassNameClash() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalClassNameClash.kt");
}
@TestMetadata("externalFunction.kt")
public void testExternalFunction() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalFunction.kt");
}
@TestMetadata("externalFunctionNameClash.kt")
public void testExternalFunctionNameClash() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalFunctionNameClash.kt");
}
@TestMetadata("externalObject.kt")
public void testExternalObject() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalObject.kt");
}
@TestMetadata("externalPackage.kt")
public void testExternalPackage() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalPackage.kt");
}
@TestMetadata("externalPackageInDifferentFile.kt")
public void testExternalPackageInDifferentFile() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalPackageInDifferentFile.kt");
}
@TestMetadata("externalProperty.kt")
public void testExternalProperty() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/externalProperty.kt");
}
@TestMetadata("interfaces.kt")
public void testInterfaces() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/interfaces.kt");
}
@TestMetadata("topLevelVarargFun.kt")
public void testTopLevelVarargFun() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsModule/topLevelVarargFun.kt");
}
}
@TestMetadata("js/js.translator/testData/box/esModules/jsName")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JsName extends AbstractJsTranslatorWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInJsName() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/jsName"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
@TestMetadata("defaultJsName.kt")
public void testDefaultJsName() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsName/defaultJsName.kt");
}
@TestMetadata("jsTopLevelClashes.kt")
public void testJsTopLevelClashes() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsName/jsTopLevelClashes.kt");
}
}
}
@TestMetadata("js/js.translator/testData/box/jsQualifier")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JsQualifier extends AbstractJsTranslatorWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInJsQualifier() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/jsQualifier"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
@TestMetadata("classes.kt")
public void testClasses() throws Exception {
runTest("js/js.translator/testData/box/jsQualifier/classes.kt");
}
@TestMetadata("interfaces.kt")
public void testInterfaces() throws Exception {
runTest("js/js.translator/testData/box/jsQualifier/interfaces.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("js/js.translator/testData/box/jsQualifier/simple.kt");
}
}
}
@@ -1,6 +1,9 @@
// DONT_TARGET_EXACT_BACKEND: JS
// ES_MODULES
// Generated .mjs name is different in Wasm
// DONT_TARGET_EXACT_BACKEND: WASM
// FILE: main.kt
interface I {
fun ok(): String
@@ -2,6 +2,9 @@
// ES_MODULES
// DONT_TARGET_EXACT_BACKEND: JS
// DONT_TARGET_EXACT_BACKEND: WASM
// WASM_MUTE_REASON: UNSUPPORTED_JS_INTEROP
package foo
@JsModule("./externalClassWithDefaults.mjs")
@@ -2,6 +2,9 @@
// ES_MODULES
// DONT_TARGET_EXACT_BACKEND: JS
// DONT_TARGET_EXACT_BACKEND: WASM
// WASM_MUTE_REASON: UNSUPPORTED_JS_INTEROP
package foo
@JsModule("./externalConstructor.mjs")
@@ -1,5 +1,9 @@
// IGNORE_FIR
// EXPECTED_REACHABLE_NODES: 1284
// DONT_TARGET_EXACT_BACKEND: WASM
// WASM_MUTE_REASON: MODULE_KIND
// NO_JS_MODULE_SYSTEM
// MODULE: lib
// MODULE_KIND: UMD
@@ -1,5 +1,9 @@
// IGNORE_FIR
// EXPECTED_REACHABLE_NODES: 1282
// DONT_TARGET_EXACT_BACKEND: WASM
// WASM_MUTE_REASON: MODULE_KIND
// MODULE: lib
// MODULE_KIND: AMD
// FILE: lib.kt
@@ -37,4 +37,65 @@ public actual annotation class JsExport {
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
public actual annotation class JsName(actual val name: String)
public actual annotation class JsName(actual val name: String)
/**
* Denotes an `external` declaration that must be imported from JavaScript module.
*
* The annotation can be used on top-level external declarations (classes, properties, functions) and files.
* In case of file (which can't be `external`) the following rule applies: all the declarations in
* the file must be `external`. By applying `@JsModule(...)` on a file you tell the compiler to import a JavaScript object
* that contain all the declarations from the file.
*
* Example:
*
* ``` kotlin
* @JsModule("jquery")
* external abstract class JQuery() {
* // some declarations here
* }
*
* @JsModule("jquery")
* external fun JQuery(element: Element): JQuery
* ```
*
* @property import name of a module to import declaration from.
* It is not interpreted by the Kotlin compiler, it's passed as is directly to the target module system.
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.FILE)
public annotation class JsModule(val import: String)
/**
* Adds prefix to `external` declarations in a source file.
*
* JavaScript does not have concept of packages (namespaces). They are usually emulated by nested objects.
* The compiler turns references to `external` declarations either to plain unprefixed names
* or to plain imports.
* However, if a JavaScript library provides its declarations in packages, you won't be satisfied with this.
* You can tell the compiler to generate additional prefix before references to `external` declarations using the `@JsQualifier(...)`
* annotation.
*
* Note that a file marked with the `@JsQualifier(...)` annotation can't contain non-`external` declarations.
*
* Example:
*
* ```
* @file:JsQualifier("my.jsPackageName")
* package some.kotlinPackage
*
* external fun foo(x: Int)
*
* external fun bar(): String
* ```
*
* @property value the qualifier to add to the declarations in the generated code.
* It must be a sequence of valid JavaScript identifiers separated by the `.` character.
* Examples of valid qualifiers are: `foo`, `bar.Baz`, `_.$0.f`.
*
* @see JsModule
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FILE)
public annotation class JsQualifier(val value: String)