[Wasm] Support JsModule and JsQualifier
This commit is contained in:
committed by
Space Team
parent
75d3ae4466
commit
d14d4c8510
+4
-1
@@ -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');
|
||||
|
||||
+15
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
+46
-5
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Generated
+18
@@ -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 {
|
||||
|
||||
Generated
+18
@@ -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 {
|
||||
|
||||
+15
@@ -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");
|
||||
|
||||
+158
@@ -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)
|
||||
Reference in New Issue
Block a user