From a08b944c2875644eb70da821a9e0df793ea50c5e Mon Sep 17 00:00:00 2001 From: Gregory Lureau Date: Thu, 24 Feb 2022 15:08:25 +0100 Subject: [PATCH] Handle top-level functions. --- build.gradle.kts | 2 +- .../kustomexport/compiler/ExportCompiler.kt | 15 +- .../kustomexport/compiler/js/Structs.kt | 5 + .../js/pattern/ClassDeclarationParser.kt | 105 +++++++------ .../compiler/js/pattern/SharedTransformer.kt | 139 +++++++++++------- .../pattern/function/FunctionTransformer.kt | 55 +++++++ .../pattern/interface/InterfaceTransformer.kt | 5 +- .../deezer/kustomexport/KustomExport.kt | 2 +- .../sample/toplevelfunction/Function.kt | 26 ++++ .../sample/toplevelfunction/Function.ts | 15 ++ 10 files changed, 267 insertions(+), 102 deletions(-) create mode 100644 compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/function/FunctionTransformer.kt create mode 100644 samples/src/commonMain/kotlin/sample/toplevelfunction/Function.kt create mode 100644 samples/src/commonMain/kotlin/sample/toplevelfunction/Function.ts diff --git a/build.gradle.kts b/build.gradle.kts index 315aef2..07b3bb5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,7 @@ plugins { allprojects { group = "deezer.kustomexport" - version = "0.3.1" + version = "0.4.0" repositories { mavenLocal() diff --git a/compiler/src/main/kotlin/deezer/kustomexport/compiler/ExportCompiler.kt b/compiler/src/main/kotlin/deezer/kustomexport/compiler/ExportCompiler.kt index 2d370a8..6786ae6 100644 --- a/compiler/src/main/kotlin/deezer/kustomexport/compiler/ExportCompiler.kt +++ b/compiler/src/main/kotlin/deezer/kustomexport/compiler/ExportCompiler.kt @@ -27,6 +27,7 @@ import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSTypeAlias import com.google.devtools.ksp.symbol.KSTypeReference @@ -41,11 +42,14 @@ import deezer.kustomexport.compiler.js.ClassDescriptor import deezer.kustomexport.compiler.js.EnumDescriptor import deezer.kustomexport.compiler.js.InterfaceDescriptor import deezer.kustomexport.compiler.js.SealedClassDescriptor +import deezer.kustomexport.compiler.js.TopLevelFunctionDescriptor import deezer.kustomexport.compiler.js.ValueClassDescriptor import deezer.kustomexport.compiler.js.pattern.`class`.transform import deezer.kustomexport.compiler.js.pattern.`interface`.transform import deezer.kustomexport.compiler.js.pattern.enum.transform +import deezer.kustomexport.compiler.js.pattern.function.transform import deezer.kustomexport.compiler.js.pattern.parseClass +import deezer.kustomexport.compiler.js.pattern.parseFunction import deezer.kustomexport.compiler.js.pattern.value.transform // Trick to share the Logger everywhere without injecting the dependency everywhere @@ -170,6 +174,14 @@ class ExportCompiler(private val environment: SymbolProcessorEnvironment) : Symb parseAndWrite(targetClassDeclaration, targetTypeNames) } + override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) { + super.visitFunctionDeclaration(function, data) + val descriptor: TopLevelFunctionDescriptor = parseFunction(function) + Logger.warn("FUNCTION: ${function.simpleName.asString()}") + descriptor.transform() + .writeCode(environment, function.containingFile!!) + } + private fun parseAndWrite( classDeclaration: KSClassDeclaration, targetTypeNames: List>, @@ -188,9 +200,6 @@ class ExportCompiler(private val environment: SymbolProcessorEnvironment) : Symb .writeCode(environment, *allSources) is InterfaceDescriptor -> descriptor.transform() .writeCode(environment, *allSources) - null -> { - // Cannot parse this class, parsing error already reported on the parser - } } } } diff --git a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/Structs.kt b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/Structs.kt index 2c44e0f..a6d6b05 100644 --- a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/Structs.kt +++ b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/Structs.kt @@ -42,6 +42,11 @@ data class ParameterDescriptor( inline fun portMethod(import: Boolean) = if (import) importedMethod else exportedMethod } +data class TopLevelFunctionDescriptor( + val packageName: String, + val function: FunctionDescriptor, +) + data class FunctionDescriptor( val name: String, val isOverride: Boolean, diff --git a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/ClassDeclarationParser.kt b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/ClassDeclarationParser.kt index 1710104..a9fa4d7 100644 --- a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/ClassDeclarationParser.kt +++ b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/ClassDeclarationParser.kt @@ -19,7 +19,6 @@ package deezer.kustomexport.compiler.js.pattern -import com.google.devtools.ksp.getAllSuperTypes import com.google.devtools.ksp.getConstructors import com.google.devtools.ksp.getDeclaredFunctions import com.google.devtools.ksp.getDeclaredProperties @@ -27,6 +26,9 @@ import com.google.devtools.ksp.isPrivate import com.google.devtools.ksp.isPublic import com.google.devtools.ksp.symbol.ClassKind import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSName +import com.google.devtools.ksp.symbol.KSTypeParameter import com.google.devtools.ksp.symbol.Modifier import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview @@ -45,49 +47,37 @@ import deezer.kustomexport.compiler.js.PropertyDescriptor import deezer.kustomexport.compiler.js.SealedClassDescriptor import deezer.kustomexport.compiler.js.SealedSubClassDescriptor import deezer.kustomexport.compiler.js.SuperDescriptor +import deezer.kustomexport.compiler.js.TopLevelFunctionDescriptor import deezer.kustomexport.compiler.js.TypeParameterDescriptor import deezer.kustomexport.compiler.js.ValueClassDescriptor import deezer.kustomexport.compiler.js.mapping.OriginTypeName +fun parseFunction( + function: KSFunctionDeclaration, + forcedConcreteTypeParameters: List>? = null, +): TopLevelFunctionDescriptor = TopLevelFunctionDescriptor( + packageName = function.packageName.asString(), + function = function.toDescriptor( + sequenceOf(), + function.typeParameters.toTypeParameterResolver(), + buildConcreteTypeParameters(forcedConcreteTypeParameters) { function.typeParameters[0] } + ) +) + @KotlinPoetKspPreview fun parseClass( classDeclaration: KSClassDeclaration, forcedConcreteTypeParameters: List>? = null, exportedClassSimpleName: String -): Descriptor? { +): Descriptor { val typeParamResolver = classDeclaration.typeParameters.toTypeParameterResolver() - val concreteTypeParameters: MutableList = mutableListOf() - (forcedConcreteTypeParameters ?: emptyList())/* ?: typeParamResolver.parametersMap.values*/ - .forEach { (name, type) -> - val className = try { - type.asClassName() - } catch (t: Throwable) { - Logger.error( - "Cannot use @KustomException on a not concrete generics class.", - classDeclaration.typeParameters[0] - ) - return null - } - concreteTypeParameters.add( - TypeParameterDescriptor( - name = name, - origin = className.cached(concreteTypeParameters), - ) - ) - } + val concreteTypeParameters: MutableList = + buildConcreteTypeParameters(forcedConcreteTypeParameters) { classDeclaration.typeParameters[0] } val packageName = classDeclaration.packageName.asString() val classSimpleName = classDeclaration.simpleName.asString() - //val superTypes = classDeclaration.getAllSuperTypes() - - Logger.warn( - "PARSING - ${classSimpleName} ${classDeclaration.superTypes.count()} ${ - classDeclaration.getAllSuperTypes().count() - }" - ) - val superTypes = classDeclaration.superTypes .map { superType -> val superTypeName = superType.toTypeNamePatch(typeParamResolver).cached(concreteTypeParameters) @@ -187,6 +177,32 @@ fun parseClass( } } +private fun buildConcreteTypeParameters( + forcedConcreteTypeParameters: List>?, + firstTypeParameterProvider: () -> KSTypeParameter +): MutableList { + val concreteTypeParameters: MutableList = mutableListOf() + (forcedConcreteTypeParameters ?: emptyList())/* ?: typeParamResolver.parametersMap.values*/ + .forEach { (name, type) -> + val className = try { + type.asClassName() + } catch (t: Throwable) { + Logger.error( + "Cannot use @KustomException on a not concrete generics class.", + firstTypeParameterProvider() + ) + error(t) + } + concreteTypeParameters.add( + TypeParameterDescriptor( + name = name, + origin = className.cached(concreteTypeParameters), + ) + ) + } + return concreteTypeParameters +} + private val nonExportableFunctions = listOf( "", "equals", @@ -216,21 +232,28 @@ fun KSClassDeclaration.parseFunctions( .filter { it.simpleName.asString() !in nonExportableFunctions } .filter { it.isPublic() } .map { func -> - FunctionDescriptor( - name = func.simpleName.asString(), - isOverride = func.findOverridee() != null || !declaredNames.contains(func.simpleName), - isSuspend = func.modifiers.contains(Modifier.SUSPEND), - returnType = func.returnType!!.toTypeNamePatch(typeParamResolver).cached(concreteTypeParameters), - parameters = func.parameters.map { p -> - ParameterDescriptor( - name = p.name?.asString() ?: TODO("not sure what we want here"), - type = p.type.toTypeNamePatch(typeParamResolver).cached(concreteTypeParameters), - ) - } - ) + func.toDescriptor(declaredNames, typeParamResolver, concreteTypeParameters) }.toList() } +private fun KSFunctionDeclaration.toDescriptor( + declaredNames: Sequence, + typeParamResolver: TypeParameterResolver, + concreteTypeParameters: MutableList +) = + FunctionDescriptor( + name = simpleName.asString(), + isOverride = findOverridee() != null || !declaredNames.contains(simpleName), + isSuspend = modifiers.contains(Modifier.SUSPEND), + returnType = returnType!!.toTypeNamePatch(typeParamResolver).cached(concreteTypeParameters), + parameters = parameters.map { p -> + ParameterDescriptor( + name = p.name?.asString() ?: TODO("not sure what we want here"), + type = p.type.toTypeNamePatch(typeParamResolver).cached(concreteTypeParameters), + ) + } + ) + @OptIn(KotlinPoetKspPreview::class) fun KSClassDeclaration.parseProperties( typeParamResolver: TypeParameterResolver, diff --git a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/SharedTransformer.kt b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/SharedTransformer.kt index 7eb73c6..ce6bb05 100644 --- a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/SharedTransformer.kt +++ b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/SharedTransformer.kt @@ -21,9 +21,11 @@ import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.STRING +import com.squareup.kotlinpoet.TypeName import deezer.kustomexport.compiler.js.FormatString import deezer.kustomexport.compiler.js.FunctionDescriptor import deezer.kustomexport.compiler.js.MethodNameDisambiguation +import deezer.kustomexport.compiler.js.ParameterDescriptor import deezer.kustomexport.compiler.js.PropertyDescriptor import deezer.kustomexport.compiler.js.abortController import deezer.kustomexport.compiler.js.abortSignal @@ -36,85 +38,116 @@ import deezer.kustomexport.compiler.js.coroutinesPromiseFunc import deezer.kustomexport.compiler.js.mapping.INDENTATION import deezer.kustomexport.compiler.js.toFormatString +enum class OverrideMode { FORCE_OVERRIDE, FORCE_NO_OVERRIDE, AUTO } + +fun FunctionDescriptor.returnType( + import: Boolean, +): TypeName { + val returns = if (import) { + returnType.concreteTypeName + } else { + returnType.exportedTypeName + } + return if (!import && isSuspend) returns.asCoroutinesPromise() else returns +} + +fun FunSpec.Builder.addParameters( + params: List, + import: Boolean, + isSuspend: Boolean +): FunSpec.Builder { + params.forEach { param -> + if (import) { + addParameter(param.name, param.type.concreteTypeName) + } else { + addParameter(param.name, param.type.exportedTypeName) + } + } + if (!import && isSuspend) { + addParameter("abortSignal", abortSignal) + } + return this +} + +//TODO: Rework and cut that spaghetti design (too much params / different use cases) fun FunctionDescriptor.buildWrappingFunction( body: Boolean, import: Boolean, - delegateName: String, + delegateName: String?, mnd: MethodNameDisambiguation, isClassOpen: Boolean, - forceOverride: Boolean = false, // TODO: rework that shortcut for testing... + overrideMode: OverrideMode = OverrideMode.AUTO, // TODO: rework that shortcut for testing... ): FunSpec { val funExportedName = mnd.getMethodName(this) val fb = FunSpec.builder(if (!import) funExportedName else name) if (isClassOpen) fb.addModifiers(KModifier.OPEN) - val returns = if (import) { - returnType.concreteTypeName - } else { - returnType.exportedTypeName - } - fb.returns(if (!import && isSuspend) returns.asCoroutinesPromise() else returns) + fb.returns(returnType(import)) - if (forceOverride || isOverride) { + if (overrideMode != OverrideMode.FORCE_NO_OVERRIDE && + (overrideMode == OverrideMode.FORCE_OVERRIDE || isOverride) + ) { fb.addModifiers(KModifier.OVERRIDE) } if (import && isSuspend) { fb.addModifiers(KModifier.SUSPEND) } - parameters.forEach { param -> - if (import) { - fb.addParameter(param.name, param.type.concreteTypeName) - } else { - fb.addParameter(param.name, param.type.exportedTypeName) - } - } - if (!import && isSuspend) { - fb.addParameter("abortSignal", abortSignal) - } + fb.addParameters(parameters, import, isSuspend) if (body) { - if (!import && isSuspend) { - fb.addCode("return %T.%M·{\n", coroutinesGlobalScope, coroutinesPromiseFunc) - } - if (import && isSuspend) { - fb.addStatement("val abortController = %T()", abortController) - fb.addStatement("val abortSignal = abortController.signal") - fb.addStatement( - "%M.%M.invokeOnCompletion { abortController.abort() }", - coroutinesContext, - coroutinesContextJob - ) - } - if (!import && isSuspend) { - fb.addStatement("abortSignal.onabort = { %M.%M.cancel() }", coroutinesContext, coroutinesContextJob) - } - val funcName = if (import) funExportedName else name - var params = parameters.fold(FormatString("")) { acc, item -> - acc + "$INDENTATION${item.name} = ".toFormatString() + item.portMethod(!import) + ",\n" - } - if (import && isSuspend) { - params += FormatString("${INDENTATION}abortSignal = abortSignal") - } - - //TODO: Opti : could save the local "result" variable here fb.addCode( - ("val result = $delegateName.$funcName(".toFormatString() + - (if (parameters.isNotEmpty()) "\n" else "") + - params + - (if (parameters.isNotEmpty()) ")\n" else ")\n")).asCode() + buildWrappingFunctionBody( + import = import, + receiver = delegateName, + functionName = if (import) funExportedName else name + ).asCode() ) + } + return fb.build() +} - fb.addCode( - ((if (import || !isSuspend) "return·" else "").toFormatString() + - returnType.portMethod(import, "result".toFormatString()) + - (if (import && isSuspend) ".%M()".toFormatString(coroutinesAwait) else "".toFormatString())).asCode() +fun FunctionDescriptor.buildWrappingFunctionBody( + import: Boolean, + receiver: String?, + functionName: String +): FormatString { + var body = "".toFormatString() + if (!import && isSuspend) { + body += "return %T.%M·{\n".toFormatString(coroutinesGlobalScope, coroutinesPromiseFunc) + } + if (import && isSuspend) { + body += "val abortController = %T()\n".toFormatString(abortController) + body += "val abortSignal = abortController.signal\n" + body += "%M.%M.invokeOnCompletion { abortController.abort() }\n".toFormatString( + coroutinesContext, coroutinesContextJob ) + } + if (!import && isSuspend) { + body += "abortSignal.onabort = { %M.%M.cancel() }\n".toFormatString(coroutinesContext, coroutinesContextJob) + } - if (!import && isSuspend) fb.addCode("\n}") + var params = parameters.fold(FormatString("")) { acc, item -> + acc + "$INDENTATION${item.name} = ".toFormatString() + item.portMethod(!import) + ",\n" } - return fb.build() + if (import && isSuspend) { + params += FormatString("${INDENTATION}abortSignal = abortSignal") + } + + //TODO: Opti : could save the local "result" variable here + val callStr = if (receiver == null) "$functionName" else "$receiver.$functionName" + body += "val result = $callStr(" + body += if (parameters.isNotEmpty()) "\n" else "" + body += params + body += if (parameters.isNotEmpty()) ")\n" else ")\n" + + body += if (import || !isSuspend) "return·" else "" + body += returnType.portMethod(import, "result".toFormatString()) + body += if (import && isSuspend) ".%M()".toFormatString(coroutinesAwait) else "".toFormatString() + + if (!import && isSuspend) body += "\n}" + return body } fun overrideGetterSetter( diff --git a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/function/FunctionTransformer.kt b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/function/FunctionTransformer.kt new file mode 100644 index 0000000..826307d --- /dev/null +++ b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/function/FunctionTransformer.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2022 Deezer. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package deezer.kustomexport.compiler.js.pattern.function + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName +import deezer.kustomexport.compiler.js.TopLevelFunctionDescriptor +import deezer.kustomexport.compiler.js.jsExport +import deezer.kustomexport.compiler.js.jsPackage +import deezer.kustomexport.compiler.js.pattern.addParameters +import deezer.kustomexport.compiler.js.pattern.buildWrappingFunctionBody +import deezer.kustomexport.compiler.js.pattern.returnType + +fun TopLevelFunctionDescriptor.transform() = transformFunction(this) + +fun transformFunction(origin: TopLevelFunctionDescriptor): FileSpec { + val function = origin.function + val commonFunctionName = "common${function.name.capitalize()}" + val functionMemberName = MemberName(origin.packageName, function.name) + val jsClassPackage = origin.packageName.jsPackage() + + val funBuilder = FunSpec.builder(function.name) + .addAnnotation(jsExport) + .returns(function.returnType(false)) + .addParameters(function.parameters, false, function.isSuspend) + funBuilder.addCode( + function.buildWrappingFunctionBody( + import = false, + receiver = null, + functionName = commonFunctionName + ).asCode() + ) + + return FileSpec.builder(jsClassPackage, function.name) + .addAliasedImport(functionMemberName, commonFunctionName) + .addFunction(funBuilder.build()) + .build() +} \ No newline at end of file diff --git a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/interface/InterfaceTransformer.kt b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/interface/InterfaceTransformer.kt index 3225079..5860fcb 100644 --- a/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/interface/InterfaceTransformer.kt +++ b/compiler/src/main/kotlin/deezer/kustomexport/compiler/js/pattern/interface/InterfaceTransformer.kt @@ -33,11 +33,10 @@ import deezer.kustomexport.compiler.js.PropertyDescriptor import deezer.kustomexport.compiler.js.jsExport import deezer.kustomexport.compiler.js.jsPackage import deezer.kustomexport.compiler.js.mapping.INDENTATION -import deezer.kustomexport.compiler.js.pattern.asClassName +import deezer.kustomexport.compiler.js.pattern.OverrideMode import deezer.kustomexport.compiler.js.pattern.buildWrappingFunction import deezer.kustomexport.compiler.js.pattern.overrideGetterSetter import deezer.kustomexport.compiler.js.pattern.packageName -import deezer.kustomexport.compiler.js.pattern.simpleName import java.util.Locale fun InterfaceDescriptor.transform() = transformInterface(this) @@ -189,7 +188,7 @@ private fun buildWrapperClass( import = import, delegateName = delegateName, mnd = mnd, - forceOverride = true, + overrideMode = OverrideMode.FORCE_OVERRIDE, isClassOpen = false, // already an interface, so "open" is not required ) ) diff --git a/lib/src/commonMain/kotlin/deezer/kustomexport/KustomExport.kt b/lib/src/commonMain/kotlin/deezer/kustomexport/KustomExport.kt index 261d309..3a6a96b 100644 --- a/lib/src/commonMain/kotlin/deezer/kustomexport/KustomExport.kt +++ b/lib/src/commonMain/kotlin/deezer/kustomexport/KustomExport.kt @@ -31,7 +31,7 @@ enum class ExportMode { } @Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS) +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.FUNCTION) public annotation class KustomExport( public val mode: ExportMode = ExportMode.IMPORT_EXPORT, ) diff --git a/samples/src/commonMain/kotlin/sample/toplevelfunction/Function.kt b/samples/src/commonMain/kotlin/sample/toplevelfunction/Function.kt new file mode 100644 index 0000000..fdcc913 --- /dev/null +++ b/samples/src/commonMain/kotlin/sample/toplevelfunction/Function.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Deezer. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package sample.toplevelfunction + +import deezer.kustomexport.KustomExport + +@KustomExport +fun adds(a: Float, b: Long): Float = a + b + +@KustomExport +suspend fun addsAsync(a: Float, b: Long): Float = a + b diff --git a/samples/src/commonMain/kotlin/sample/toplevelfunction/Function.ts b/samples/src/commonMain/kotlin/sample/toplevelfunction/Function.ts new file mode 100644 index 0000000..6842e9e --- /dev/null +++ b/samples/src/commonMain/kotlin/sample/toplevelfunction/Function.ts @@ -0,0 +1,15 @@ +import { runTest } from "../shared_ts/RunTest" +import { assert, assertEquals, assertQuiet, assertEqualsQuiet } from "../shared_ts/Assert" +import { sample } from '@kustom/Samples' + +runTest("Top-level function", async () : Promise => { + // First param of adds is Float, second is Long so decimal part (0.4) is ignored + assertEquals(8.2, sample.toplevelfunction.js.adds(3.2, 5.4), "is supported") + + var abortController = new AbortController() + var abortSignal = abortController.signal + sample.toplevelfunction.js.addsAsync(3.2, 5.4, abortSignal) + .then((res) => { + assertEquals(8.2, res, "suspend is supported") + }) +})