Skip to content

Commit 5bb4b11

Browse files
authored
JS-export QName, BigDecimal, ELM classes (#1739)
* JS-export QName, BigDecimal, ELM classes * Fix type name for Kotlin Longs in JS
1 parent 71926a9 commit 5bb4b11

6 files changed

Lines changed: 99 additions & 15 deletions

File tree

Src/java/build-logic/src/main/kotlin/XsdKotlinGenTask.kt

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import javax.xml.parsers.SAXParserFactory
1313
import org.gradle.api.DefaultTask
1414
import org.gradle.api.tasks.TaskAction
1515

16-
data class Config(val project: String, val xsd: String, val outputDir: String)
16+
data class Config(
17+
val project: String,
18+
val xsd: String,
19+
val outputDir: String,
20+
val jsExport: Boolean,
21+
)
1722

1823
// Entry points for schema parsing and code generation
1924
val configs =
@@ -22,11 +27,13 @@ val configs =
2227
project = "cql",
2328
xsd = "../../cql-lm/schema/model/modelinfo.xsd",
2429
outputDir = "../cql/build/generated/sources/cql/commonMain/kotlin",
30+
jsExport = false,
2531
),
2632
Config(
2733
project = "elm",
2834
xsd = "../../cql-lm/schema/elm/library.xsd",
2935
outputDir = "../elm/build/generated/sources/elm/commonMain/kotlin",
36+
jsExport = true,
3037
),
3138
)
3239

@@ -350,7 +357,7 @@ fun getOwnAttributes(complexType: XSComplexType): List<XSAttributeUse> {
350357
}
351358

352359
// Creates a class for a complex type with nested classes for nested anonymous complex types
353-
fun buildClass(complexType: XSComplexType, className: ClassName): TypeSpec {
360+
fun getClassBuilder(complexType: XSComplexType, className: ClassName): TypeSpec.Builder {
354361
return TypeSpec.classBuilder(className)
355362
.apply {
356363
if (complexType.isAbstract) {
@@ -503,7 +510,10 @@ fun buildClass(complexType: XSComplexType, className: ClassName): TypeSpec {
503510
)
504511

505512
// Add a nested class for the anonymous complex type
506-
addType(buildClass(elementDecl.type.asComplexType(), nestedClassName))
513+
addType(
514+
getClassBuilder(elementDecl.type.asComplexType(), nestedClassName)
515+
.build()
516+
)
507517

508518
addElement(elementDecl, nestedClassName, className, particle.isRepeated)
509519
} else {
@@ -608,7 +618,6 @@ fun buildClass(complexType: XSComplexType, className: ClassName): TypeSpec {
608618
.build()
609619
)
610620
.addType(TypeSpec.companionObjectBuilder().build())
611-
.build()
612621
}
613622

614623
// Returns all subtypes of a complex type (including indirect subtypes)
@@ -1124,6 +1133,29 @@ fun FileSpec.Builder.addSerializers(
11241133
return this
11251134
}
11261135

1136+
// Adds `@JsOnlyExport`
1137+
fun TypeSpec.Builder.addJsExport(): TypeSpec.Builder {
1138+
addAnnotation(
1139+
AnnotationSpec.builder(ClassName("kotlin", "OptIn"))
1140+
.addMember("%T::class", ClassName("kotlin.js", "ExperimentalJsExport"))
1141+
.build()
1142+
)
1143+
addAnnotation(ClassName("org.cqframework.cql.shared", "JsOnlyExport"))
1144+
1145+
return this
1146+
}
1147+
1148+
// Adds `@file:Suppress("UNNECESSARY_SAFE_CALL")`
1149+
fun FileSpec.Builder.addSuppressSafeCalls(): FileSpec.Builder {
1150+
addAnnotation(
1151+
AnnotationSpec.builder(ClassName("kotlin", "Suppress"))
1152+
.addMember("%S", "UNNECESSARY_SAFE_CALL")
1153+
.build()
1154+
)
1155+
1156+
return this
1157+
}
1158+
11271159
open class XsdKotlinGenTask : DefaultTask() {
11281160

11291161
@TaskAction
@@ -1156,6 +1188,11 @@ open class XsdKotlinGenTask : DefaultTask() {
11561188
FileSpec.builder(typeName)
11571189
.addType(
11581190
TypeSpec.enumBuilder(typeName)
1191+
.apply {
1192+
if (config.jsExport) {
1193+
addJsExport()
1194+
}
1195+
}
11591196
.primaryConstructor(
11601197
FunSpec.constructorBuilder()
11611198
.addParameter("value", String::class)
@@ -1226,8 +1263,17 @@ open class XsdKotlinGenTask : DefaultTask() {
12261263

12271264
// Generate the class and XML and JSON parsers and serializers
12281265
FileSpec.builder(className)
1229-
.addType(buildClass(complexType, className))
1266+
.addType(
1267+
getClassBuilder(complexType, className)
1268+
.apply {
1269+
if (config.jsExport) {
1270+
addJsExport()
1271+
}
1272+
}
1273+
.build()
1274+
)
12301275
.addSerializers(complexType, className)
1276+
.addSuppressSafeCalls()
12311277
.build()
12321278
.writeTo(File(project.projectDir, config.outputDir))
12331279
}

Src/java/build-logic/src/main/kotlin/cql.kotlin-multiplatform-conventions.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ kotlin {
4848
jvm()
4949

5050
js {
51+
compilerOptions {
52+
// Enable support for BigInt
53+
freeCompilerArgs.add("-Xes-long-as-bigint")
54+
}
5155
useEsModules()
5256
browser {
5357
testTask {

Src/java/engine/src/commonMain/kotlin/org/opencds/cqf/cql/engine/util/ReflectionJs.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ data class JavaClassJs<T : Any>(private val kClass: KClass<T>) {
3030
fun getName(): String {
3131
return when (kClass) {
3232
Int::class -> "Integer"
33+
Long::class -> "Long"
3334
BigDecimal::class -> "BigDecimal"
3435
else -> kClass.simpleName ?: unknownSimpleName
3536
}

Src/java/shared/src/commonMain/kotlin/org/cqframework/cql/shared/BigDecimalJs.kt

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1+
@file:OptIn(ExperimentalJsExport::class)
2+
13
package org.cqframework.cql.shared
24

35
import com.ionspin.kotlin.bignum.decimal.BigDecimal as KtBigDecimal
46
import com.ionspin.kotlin.bignum.decimal.DecimalMode
57
import com.ionspin.kotlin.bignum.decimal.RoundingMode as KtRoundingMode
8+
import kotlin.js.ExperimentalJsExport
9+
import kotlin.js.JsExport
10+
import kotlin.js.JsName
611

712
/** A minimal pure-Kotlin implementation of BigDecimal for non-Java environments. */
813
@Suppress("TooManyFunctions")
9-
data class BigDecimalJs(private val value: KtBigDecimal) {
10-
constructor(value: Int) : this(KtBigDecimal.fromInt(value))
14+
@JsOnlyExport
15+
@JsName("BigDecimal")
16+
class BigDecimalJs private constructor(private val value: KtBigDecimal) {
17+
@JsName("fromInt") constructor(value: Int) : this(KtBigDecimal.fromInt(value))
1118

12-
constructor(value: Long) : this(KtBigDecimal.fromLong(value))
19+
@JsName("fromLong") constructor(value: Long) : this(KtBigDecimal.fromLong(value))
1320

21+
@JsName("fromString")
1422
constructor(
1523
value: String
1624
) : this(
@@ -25,7 +33,7 @@ data class BigDecimalJs(private val value: KtBigDecimal) {
2533
)
2634
)
2735

28-
constructor(value: Double) : this(KtBigDecimal.fromDouble(value))
36+
@JsName("fromDouble") constructor(value: Double) : this(KtBigDecimal.fromDouble(value))
2937

3038
fun toPlainString(): String {
3139
return this.value.toPlainString()
@@ -43,10 +51,12 @@ data class BigDecimalJs(private val value: KtBigDecimal) {
4351
return this.value.scale.toInt()
4452
}
4553

54+
@JsExport.Ignore
4655
fun setScale(scale: Int): BigDecimalJs {
4756
return BigDecimalJs(this.value.scale(scale.toLong()))
4857
}
4958

59+
@JsExport.Ignore
5060
fun setScale(scale: Int, roundingMode: RoundingModeJs): BigDecimalJs {
5161
return BigDecimalJs(
5262
KtBigDecimal.parseStringWithMode(
@@ -84,10 +94,12 @@ data class BigDecimalJs(private val value: KtBigDecimal) {
8494
return BigDecimalJs(this.value.multiply(value.value))
8595
}
8696

97+
@JsExport.Ignore
8798
fun divide(value: BigDecimalJs): BigDecimalJs {
8899
return BigDecimalJs(this.value.divide(value.value))
89100
}
90101

102+
@JsExport.Ignore
91103
fun divide(value: BigDecimalJs, scale: Int, roundingMode: RoundingModeJs): BigDecimalJs {
92104
return BigDecimalJs(
93105
this.value.divide(
@@ -118,11 +130,27 @@ data class BigDecimalJs(private val value: KtBigDecimal) {
118130
return this.value.longValue()
119131
}
120132

133+
override fun equals(other: Any?): Boolean {
134+
if (this === other) return true
135+
136+
if (other is BigDecimalJs) {
137+
return this.value == other.value
138+
}
139+
140+
return false
141+
}
142+
143+
override fun hashCode(): Int {
144+
return this.value.hashCode()
145+
}
146+
121147
override fun toString(): String {
122148
return this.value.toString()
123149
}
124150
}
125151

152+
@JsOnlyExport
153+
@JsName("RoundingMode")
126154
enum class RoundingModeJs {
127155
CEILING,
128156
FLOOR,

Src/java/shared/src/commonMain/kotlin/org/cqframework/cql/shared/QName.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
package org.cqframework.cql.shared
44

55
/** A minimal multiplatform implementation of QName. */
6-
expect class QName {
7-
constructor(namespaceURI: String, localPart: String, prefix: String)
8-
6+
expect class QName(namespaceURI: String, localPart: String, prefix: String) {
97
constructor(namespaceURI: String, localPart: String)
108

119
constructor(localPart: String)

Src/java/shared/src/commonMain/kotlin/org/cqframework/cql/shared/QNameJs.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package org.cqframework.cql.shared
22

3+
import kotlin.js.ExperimentalJsExport
4+
import kotlin.js.JsExport
5+
import kotlin.js.JsName
6+
37
/** A minimal pure-Kotlin implementation of QName for non-Java environments. */
4-
class QNameJs
5-
constructor(
8+
@OptIn(ExperimentalJsExport::class)
9+
@JsOnlyExport
10+
@JsName("QName")
11+
class QNameJs(
612
private val namespaceURI: String,
713
private val localPart: String,
814
private val prefix: String,
915
) {
16+
@JsExport.Ignore
1017
constructor(namespaceURI: String, localPart: String) : this(namespaceURI, localPart, "")
1118

12-
constructor(localPart: String) : this("", localPart, "")
19+
@JsExport.Ignore constructor(localPart: String) : this("", localPart, "")
1320

1421
fun getPrefix(): String = prefix
1522

0 commit comments

Comments
 (0)