feat: add ability to create nested entities inside object.
This commit is contained in:
+29
-10
@@ -20,7 +20,7 @@ data class ExportedModule(
|
|||||||
|
|
||||||
class ExportedNamespace(
|
class ExportedNamespace(
|
||||||
val name: String,
|
val name: String,
|
||||||
val declarations: List<ExportedDeclaration>
|
val declarations: List<ExportedDeclaration>,
|
||||||
) : ExportedDeclaration()
|
) : ExportedDeclaration()
|
||||||
|
|
||||||
data class ExportedFunction(
|
data class ExportedFunction(
|
||||||
@@ -59,24 +59,43 @@ class ExportedProperty(
|
|||||||
val isField: Boolean,
|
val isField: Boolean,
|
||||||
val irGetter: IrFunction?,
|
val irGetter: IrFunction?,
|
||||||
val irSetter: IrFunction?,
|
val irSetter: IrFunction?,
|
||||||
val exportedObject: ExportedClass? = null,
|
|
||||||
) : ExportedDeclaration()
|
) : ExportedDeclaration()
|
||||||
|
|
||||||
|
|
||||||
// TODO: Cover all cases with frontend and disable error declarations
|
// TODO: Cover all cases with frontend and disable error declarations
|
||||||
class ErrorDeclaration(val message: String) : ExportedDeclaration()
|
class ErrorDeclaration(val message: String) : ExportedDeclaration()
|
||||||
|
|
||||||
data class ExportedClass(
|
|
||||||
val name: String,
|
sealed class ExportedClass : ExportedDeclaration() {
|
||||||
|
abstract val name: String
|
||||||
|
abstract val ir: IrClass
|
||||||
|
abstract val members: List<ExportedDeclaration>
|
||||||
|
abstract val superClass: ExportedType?
|
||||||
|
abstract val superInterfaces: List<ExportedType>
|
||||||
|
abstract val nestedClasses: List<ExportedClass>
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExportedRegularClass(
|
||||||
|
override val name: String,
|
||||||
val isInterface: Boolean = false,
|
val isInterface: Boolean = false,
|
||||||
val isAbstract: Boolean = false,
|
val isAbstract: Boolean = false,
|
||||||
val superClass: ExportedType? = null,
|
override val superClass: ExportedType? = null,
|
||||||
val superInterfaces: List<ExportedType> = emptyList(),
|
override val superInterfaces: List<ExportedType> = emptyList(),
|
||||||
val typeParameters: List<String>,
|
val typeParameters: List<String>,
|
||||||
val members: List<ExportedDeclaration>,
|
override val members: List<ExportedDeclaration>,
|
||||||
val nestedClasses: List<ExportedClass>,
|
override val nestedClasses: List<ExportedClass>,
|
||||||
val ir: IrClass
|
override val ir: IrClass,
|
||||||
) : ExportedDeclaration()
|
) : ExportedClass()
|
||||||
|
|
||||||
|
data class ExportedObject(
|
||||||
|
override val name: String,
|
||||||
|
override val superClass: ExportedType? = null,
|
||||||
|
override val superInterfaces: List<ExportedType> = emptyList(),
|
||||||
|
override val members: List<ExportedDeclaration>,
|
||||||
|
override val nestedClasses: List<ExportedClass>,
|
||||||
|
override val ir: IrClass,
|
||||||
|
val irGetter: IrFunction
|
||||||
|
) : ExportedClass()
|
||||||
|
|
||||||
class ExportedParameter(
|
class ExportedParameter(
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|||||||
+21
-37
@@ -270,9 +270,7 @@ class ExportModelGenerator(
|
|||||||
listOf(privateConstructor) + members,
|
listOf(privateConstructor) + members,
|
||||||
nestedClasses
|
nestedClasses
|
||||||
).let {
|
).let {
|
||||||
(it as ExportedClass).copy(
|
(it as ExportedRegularClass).copy(isAbstract = true)
|
||||||
isAbstract = true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,43 +350,29 @@ class ExportModelGenerator(
|
|||||||
|
|
||||||
val name = klass.getExportedIdentifier()
|
val name = klass.getExportedIdentifier()
|
||||||
|
|
||||||
val exportedClass = ExportedClass(
|
return if (klass.kind == ClassKind.OBJECT) {
|
||||||
name = name,
|
return ExportedObject(
|
||||||
isInterface = klass.isInterface,
|
ir = klass,
|
||||||
isAbstract = klass.modality == Modality.ABSTRACT || klass.modality == Modality.SEALED,
|
|
||||||
superClass = superType,
|
|
||||||
superInterfaces = superInterfaces,
|
|
||||||
typeParameters = typeParameters,
|
|
||||||
members = members,
|
|
||||||
nestedClasses = nestedClasses,
|
|
||||||
ir = klass
|
|
||||||
)
|
|
||||||
|
|
||||||
if (klass.kind == ClassKind.OBJECT) {
|
|
||||||
var t: ExportedType = ExportedType.InlineInterfaceType(members + nestedClasses)
|
|
||||||
if (superType != null)
|
|
||||||
t = ExportedType.IntersectionType(t, superType)
|
|
||||||
|
|
||||||
for (superInterface in superInterfaces) {
|
|
||||||
t = ExportedType.IntersectionType(t, superInterface)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExportedProperty(
|
|
||||||
name = name,
|
name = name,
|
||||||
type = t,
|
members = members,
|
||||||
mutable = false,
|
superClass = superType,
|
||||||
isMember = klass.parent is IrClass,
|
nestedClasses = nestedClasses,
|
||||||
isStatic = !klass.isInner,
|
superInterfaces = superInterfaces,
|
||||||
isAbstract = false,
|
irGetter = context.mapping.objectToGetInstanceFunction[klass]!!
|
||||||
isProtected = klass.visibility == DescriptorVisibilities.PROTECTED,
|
)
|
||||||
irGetter = context.mapping.objectToGetInstanceFunction[klass]!!,
|
} else {
|
||||||
irSetter = null,
|
ExportedRegularClass(
|
||||||
exportedObject = exportedClass,
|
name = name,
|
||||||
isField = false,
|
isInterface = klass.isInterface,
|
||||||
|
isAbstract = klass.modality == Modality.ABSTRACT || klass.modality == Modality.SEALED,
|
||||||
|
superClass = superType,
|
||||||
|
superInterfaces = superInterfaces,
|
||||||
|
typeParameters = typeParameters,
|
||||||
|
members = members,
|
||||||
|
nestedClasses = nestedClasses,
|
||||||
|
ir = klass
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return exportedClass
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exportAsEnumMember(
|
private fun exportAsEnumMember(
|
||||||
|
|||||||
+12
-10
@@ -86,17 +86,22 @@ class ExportModelToJsStatements(
|
|||||||
|
|
||||||
is ExportedProperty -> {
|
is ExportedProperty -> {
|
||||||
require(namespace != null) { "Only namespaced properties are allowed" }
|
require(namespace != null) { "Only namespaced properties are allowed" }
|
||||||
val underlying: List<JsStatement> = declaration.exportedObject?.let {
|
|
||||||
generateDeclarationExport(it, null, esModules)
|
|
||||||
} ?: emptyList()
|
|
||||||
val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
|
val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
|
||||||
val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
|
val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
|
||||||
listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt()) + underlying
|
listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt())
|
||||||
}
|
}
|
||||||
|
|
||||||
is ErrorDeclaration -> emptyList()
|
is ErrorDeclaration -> emptyList()
|
||||||
|
|
||||||
is ExportedClass -> {
|
is ExportedObject -> {
|
||||||
|
require(namespace != null) { "Only namespaced properties are allowed" }
|
||||||
|
val newNameSpace = jsElementAccess(declaration.name, namespace)
|
||||||
|
val getter = JsNameRef(namer.getNameForStaticDeclaration(declaration.irGetter))
|
||||||
|
val staticsExport = declaration.nestedClasses.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
|
||||||
|
listOf(defineProperty(namespace, declaration.name, getter, null).makeStmt()) + staticsExport
|
||||||
|
}
|
||||||
|
|
||||||
|
is ExportedRegularClass -> {
|
||||||
if (declaration.isInterface) return emptyList()
|
if (declaration.isInterface) return emptyList()
|
||||||
val newNameSpace = if (namespace != null)
|
val newNameSpace = if (namespace != null)
|
||||||
jsElementAccess(declaration.name, namespace)
|
jsElementAccess(declaration.name, namespace)
|
||||||
@@ -120,16 +125,13 @@ class ExportModelToJsStatements(
|
|||||||
.filter { it is ExportedFunction && it.isStatic }
|
.filter { it is ExportedFunction && it.isStatic }
|
||||||
.takeIf { !declaration.ir.isInner }.orEmpty()
|
.takeIf { !declaration.ir.isInner }.orEmpty()
|
||||||
|
|
||||||
// Nested objects are exported as static properties
|
val enumEntries = declaration.members.filter { it is ExportedProperty && it.isStatic }
|
||||||
val staticProperties = declaration.members.mapNotNull {
|
|
||||||
(it as? ExportedProperty)?.takeIf { it.isStatic }
|
|
||||||
}
|
|
||||||
|
|
||||||
val innerClassesAssignments = declaration.nestedClasses
|
val innerClassesAssignments = declaration.nestedClasses
|
||||||
.filter { it.ir.isInner }
|
.filter { it.ir.isInner }
|
||||||
.map { it.generateInnerClassAssignment(declaration) }
|
.map { it.generateInnerClassAssignment(declaration) }
|
||||||
|
|
||||||
val staticsExport = (staticFunctions + staticProperties + declaration.nestedClasses)
|
val staticsExport = (staticFunctions + enumEntries + declaration.nestedClasses)
|
||||||
.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
|
.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
|
||||||
|
|
||||||
listOfNotNull(klassExport) + staticsExport + innerClassesAssignments
|
listOfNotNull(klassExport) + staticsExport + innerClassesAssignments
|
||||||
|
|||||||
+394
-274
@@ -5,314 +5,434 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.backend.js.export
|
package org.jetbrains.kotlin.ir.backend.js.export
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.isObject
|
||||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
import org.jetbrains.kotlin.js.common.isValidES5Identifier
|
import org.jetbrains.kotlin.js.common.isValidES5Identifier
|
||||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||||
|
|
||||||
// TODO: Support module kinds other than plain
|
private const val declare = "declare"
|
||||||
|
private const val Nullable = "Nullable"
|
||||||
|
private const val doNotImplementIt = "__doNotImplementIt"
|
||||||
|
private const val objects = "_objects_"
|
||||||
|
private const val syntheticObjectNameSeparator = '$'
|
||||||
|
|
||||||
fun ExportedModule.toTypeScript(): String {
|
fun ExportedModule.toTypeScript(): String {
|
||||||
return wrapTypeScript(name, moduleKind, declarations.toTypeScript(moduleKind))
|
return ExportModelToTsDeclarations().generateTypeScript(name, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun wrapTypeScript(name: String, moduleKind: ModuleKind, dts: String): String {
|
|
||||||
val declareKeyword = when (moduleKind) {
|
|
||||||
ModuleKind.PLAIN -> ""
|
|
||||||
else -> "declare "
|
|
||||||
}
|
|
||||||
val types = """
|
|
||||||
type Nullable<T> = T | null | undefined
|
|
||||||
${declareKeyword}const __doNotImplementIt: unique symbol
|
|
||||||
type __doNotImplementIt = typeof __doNotImplementIt
|
|
||||||
""".trimIndent().prependIndent(moduleKind.indent) + "\n"
|
|
||||||
|
|
||||||
val declarationsDts = types + dts
|
|
||||||
|
|
||||||
val namespaceName = sanitizeName(name, withHash = false)
|
|
||||||
|
|
||||||
return when (moduleKind) {
|
|
||||||
ModuleKind.PLAIN -> "declare namespace $namespaceName {\n$declarationsDts\n}\n"
|
|
||||||
ModuleKind.AMD, ModuleKind.COMMON_JS, ModuleKind.ES -> declarationsDts
|
|
||||||
ModuleKind.UMD -> "$declarationsDts\nexport as namespace $namespaceName;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val ModuleKind.indent: String
|
|
||||||
get() = if (this == ModuleKind.PLAIN) " " else ""
|
|
||||||
|
|
||||||
fun List<ExportedDeclaration>.toTypeScript(moduleKind: ModuleKind): String {
|
fun List<ExportedDeclaration>.toTypeScript(moduleKind: ModuleKind): String {
|
||||||
return joinToString("\n") {
|
return ExportModelToTsDeclarations().generateTypeScript(moduleKind, this)
|
||||||
it.toTypeScript(
|
}
|
||||||
indent = moduleKind.indent,
|
|
||||||
prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export "
|
// TODO: Support module kinds other than plain
|
||||||
|
class ExportModelToTsDeclarations {
|
||||||
|
private val objectsSyntheticProperties = mutableListOf<ExportedProperty>()
|
||||||
|
|
||||||
|
private val ModuleKind.indent: String
|
||||||
|
get() = if (this == ModuleKind.PLAIN) " " else ""
|
||||||
|
|
||||||
|
fun generateTypeScript(name: String, module: ExportedModule): String {
|
||||||
|
val declareKeyword = when (module.moduleKind) {
|
||||||
|
ModuleKind.PLAIN -> ""
|
||||||
|
else -> "$declare "
|
||||||
|
}
|
||||||
|
val types = """
|
||||||
|
type $Nullable<T> = T | null | undefined
|
||||||
|
${declareKeyword}const $doNotImplementIt: unique symbol
|
||||||
|
type $doNotImplementIt = typeof $doNotImplementIt
|
||||||
|
""".trimIndent().prependIndent(module.moduleKind.indent) + "\n"
|
||||||
|
|
||||||
|
val declarationsDts = types + module.declarations.toTypeScript(module.moduleKind)
|
||||||
|
|
||||||
|
val namespaceName = sanitizeName(name, withHash = false)
|
||||||
|
|
||||||
|
return when (module.moduleKind) {
|
||||||
|
ModuleKind.PLAIN -> "declare namespace $namespaceName {\n$declarationsDts\n}\n"
|
||||||
|
ModuleKind.AMD, ModuleKind.COMMON_JS, ModuleKind.ES -> declarationsDts
|
||||||
|
ModuleKind.UMD -> "$declarationsDts\nexport as namespace $namespaceName;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateTypeScript(moduleKind: ModuleKind, declarations: List<ExportedDeclaration>): String {
|
||||||
|
return declarations.toTypeScript(moduleKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ExportedDeclaration>.toTypeScript(moduleKind: ModuleKind): String {
|
||||||
|
return joinToString("\n") {
|
||||||
|
it.toTypeScript(
|
||||||
|
indent = moduleKind.indent,
|
||||||
|
prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export "
|
||||||
|
)
|
||||||
|
} + generateObjectsNamespaceIfNeeded(moduleKind.indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateObjectsNamespaceIfNeeded(indent: String): String {
|
||||||
|
return if (objectsSyntheticProperties.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
|
||||||
|
joinToString("") { it.toTypeScript(indent) + "\n" }
|
||||||
|
|
||||||
|
private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
|
||||||
|
indent + when (this) {
|
||||||
|
is ErrorDeclaration -> generateTypeScriptString()
|
||||||
|
is ExportedNamespace -> generateTypeScriptString(indent, prefix)
|
||||||
|
is ExportedFunction -> generateTypeScriptString(indent, prefix)
|
||||||
|
is ExportedConstructor -> generateTypeScriptString(indent)
|
||||||
|
is ExportedConstructSignature -> generateTypeScriptString(indent)
|
||||||
|
is ExportedProperty -> generateTypeScriptString(indent, prefix)
|
||||||
|
is ExportedObject -> generateTypeScriptString(indent, prefix)
|
||||||
|
is ExportedRegularClass -> generateTypeScriptString(indent, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ErrorDeclaration.generateTypeScriptString(): String {
|
||||||
|
return "/* ErrorDeclaration: $message */"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedNamespace.generateTypeScriptString(indent: String, prefix: String): String {
|
||||||
|
return "${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedConstructor.generateTypeScriptString(indent: String): String {
|
||||||
|
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
||||||
|
return "${visibility.keyword}constructor($renderedParameters);"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedConstructSignature.generateTypeScriptString(indent: String): String {
|
||||||
|
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
||||||
|
return "new($renderedParameters): ${returnType.toTypeScript(indent)};"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String): String {
|
||||||
|
val visibility = if (isProtected) "protected " else ""
|
||||||
|
val keyword = when {
|
||||||
|
isMember -> (if (isAbstract) "abstract " else "")
|
||||||
|
else -> if (mutable) "let " else "const "
|
||||||
|
}
|
||||||
|
val possibleStatic = if (isMember && isStatic) "static " else ""
|
||||||
|
val containsUnresolvedChar = !name.isValidES5Identifier()
|
||||||
|
val memberName = when {
|
||||||
|
isMember && containsUnresolvedChar -> "\"$name\""
|
||||||
|
else -> name
|
||||||
|
}
|
||||||
|
val typeToTypeScript = type.toTypeScript(indent)
|
||||||
|
|
||||||
|
return if (isMember && !isField) {
|
||||||
|
val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;"
|
||||||
|
if (!mutable) {
|
||||||
|
getter
|
||||||
|
} else {
|
||||||
|
getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isMember && containsUnresolvedChar) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
val readonly = if (isMember && !mutable) "readonly " else ""
|
||||||
|
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedFunction.generateTypeScriptString(indent: String, prefix: String): String {
|
||||||
|
val visibility = if (isProtected) "protected " else ""
|
||||||
|
|
||||||
|
val keyword: String = when {
|
||||||
|
isMember -> when {
|
||||||
|
isStatic -> "static "
|
||||||
|
isAbstract -> "abstract "
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
else -> "function "
|
||||||
|
}
|
||||||
|
|
||||||
|
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
||||||
|
|
||||||
|
val renderedTypeParameters = if (typeParameters.isNotEmpty()) {
|
||||||
|
"<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val renderedReturnType = returnType.toTypeScript(indent)
|
||||||
|
val containsUnresolvedChar = !name.isValidES5Identifier()
|
||||||
|
|
||||||
|
val escapedName = when {
|
||||||
|
isMember && containsUnresolvedChar -> "\"$name\""
|
||||||
|
else -> name
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (!isMember && containsUnresolvedChar) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedObject.generateTypeScriptString(indent: String, prefix: String): String {
|
||||||
|
val shouldRenderSeparatedAbstractClass = !couldBeProperty()
|
||||||
|
|
||||||
|
var t: ExportedType = ExportedType.InlineInterfaceType(members)
|
||||||
|
|
||||||
|
if (superClass != null)
|
||||||
|
t = ExportedType.IntersectionType(t, superClass)
|
||||||
|
|
||||||
|
for (superInterface in superInterfaces) {
|
||||||
|
t = ExportedType.IntersectionType(t, superInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRenderSeparatedAbstractClass) {
|
||||||
|
val constructor = ExportedConstructSignature(emptyList(), ExportedType.Primitive.Any)
|
||||||
|
t = ExportedType.IntersectionType(t, ExportedType.InlineInterfaceType(listOf(constructor)))
|
||||||
|
}
|
||||||
|
|
||||||
|
val maybeParentClass = ir.parent as? IrClass
|
||||||
|
|
||||||
|
val propertyName = ir
|
||||||
|
.takeIf { shouldRenderSeparatedAbstractClass }
|
||||||
|
?.getFqNameWithJsNameWhenAvailable(true)
|
||||||
|
?.asString()
|
||||||
|
?.replace('.', syntheticObjectNameSeparator) ?: name
|
||||||
|
|
||||||
|
val property = ExportedProperty(
|
||||||
|
name = propertyName,
|
||||||
|
type = t,
|
||||||
|
mutable = false,
|
||||||
|
isMember = maybeParentClass != null && !shouldRenderSeparatedAbstractClass,
|
||||||
|
isStatic = !ir.isInner && maybeParentClass?.isObject == false,
|
||||||
|
isAbstract = false,
|
||||||
|
isProtected = ir.visibility == DescriptorVisibilities.PROTECTED,
|
||||||
|
irGetter = irGetter,
|
||||||
|
irSetter = null,
|
||||||
|
isField = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
return if (!shouldRenderSeparatedAbstractClass) {
|
||||||
|
property.generateTypeScriptString(indent, prefix)
|
||||||
|
} else {
|
||||||
|
val propertyRef = "$objects.$propertyName"
|
||||||
|
val shouldCreateExtraProperty = members.isNotEmpty() || superInterfaces.isNotEmpty() || superClass != null
|
||||||
|
val newSuperClass = ExportedType.ClassType(propertyRef, emptyList(), ir).takeIf { shouldCreateExtraProperty }
|
||||||
|
ExportedRegularClass(
|
||||||
|
name = name,
|
||||||
|
isInterface = false,
|
||||||
|
isAbstract = true,
|
||||||
|
superClass = newSuperClass,
|
||||||
|
superInterfaces = superInterfaces,
|
||||||
|
typeParameters = emptyList(),
|
||||||
|
members = listOf(ExportedConstructor(emptyList(), ExportedVisibility.PRIVATE)),
|
||||||
|
nestedClasses = nestedClasses,
|
||||||
|
ir = ir
|
||||||
|
)
|
||||||
|
.generateTypeScriptString(indent, prefix)
|
||||||
|
.also { if (shouldCreateExtraProperty) objectsSyntheticProperties.add(property) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedRegularClass.generateTypeScriptString(indent: String, prefix: String): String {
|
||||||
|
val keyword = if (isInterface) "interface" else "class"
|
||||||
|
val superInterfacesKeyword = if (isInterface) "extends" else "implements"
|
||||||
|
|
||||||
|
val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: ""
|
||||||
|
val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent)
|
||||||
|
|
||||||
|
val (memberObjects, nestedDeclarations) = nestedClasses.partition { it.couldBeProperty() }
|
||||||
|
|
||||||
|
val members = members
|
||||||
|
.let { if (shouldNotBeImplemented()) it.withMagicProperty() else it }
|
||||||
|
.map {
|
||||||
|
if (!ir.isInner || it !is ExportedFunction || !it.isStatic) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
// Remove $outer argument from secondary constructors of inner classes
|
||||||
|
it.copy(parameters = it.parameters.drop(1))
|
||||||
|
}
|
||||||
|
} + memberObjects
|
||||||
|
|
||||||
|
val (innerClasses, nonInnerClasses) = nestedDeclarations.partition { it.ir.isInner }
|
||||||
|
val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
|
||||||
|
val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" }
|
||||||
|
|
||||||
|
// If there are no exported constructors, add a private constructor to disable default one
|
||||||
|
val privateCtorString = if (!isInterface && !isAbstract && members.none { it is ExportedConstructor }) {
|
||||||
|
"$indent private constructor();\n"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val renderedTypeParameters = if (typeParameters.isNotEmpty()) {
|
||||||
|
"<" + typeParameters.joinToString(", ") + ">"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val modifiers = if (isAbstract && !isInterface) "abstract " else ""
|
||||||
|
|
||||||
|
val bodyString = privateCtorString + membersString + indent
|
||||||
|
|
||||||
|
val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() }
|
||||||
|
val klassExport =
|
||||||
|
"$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
|
||||||
|
val staticsExport =
|
||||||
|
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(
|
||||||
|
indent,
|
||||||
|
prefix
|
||||||
|
) else ""
|
||||||
|
|
||||||
|
return if (name.isValidES5Identifier()) klassExport + staticsExport else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedType.toExtendsClause(indent: String): String {
|
||||||
|
val isImplicitlyExportedType = this is ExportedType.ImplicitlyExportedType
|
||||||
|
val extendsClause = " extends ${toTypeScript(indent, isImplicitlyExportedType)}"
|
||||||
|
return when {
|
||||||
|
isImplicitlyExportedType -> " /*$extendsClause */"
|
||||||
|
else -> extendsClause
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent: String): String {
|
||||||
|
val (exportedInterfaces, nonExportedInterfaces) = partition { it !is ExportedType.ImplicitlyExportedType }
|
||||||
|
val listOfNonExportedInterfaces = nonExportedInterfaces.joinToString(", ") {
|
||||||
|
(it as ExportedType.ImplicitlyExportedType).type.toTypeScript(indent, true)
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
exportedInterfaces.isEmpty() && nonExportedInterfaces.isNotEmpty() ->
|
||||||
|
" /* $superInterfacesKeyword $listOfNonExportedInterfaces */"
|
||||||
|
exportedInterfaces.isNotEmpty() -> {
|
||||||
|
val nonExportedInterfacesTsString = if (nonExportedInterfaces.isNotEmpty()) "/*, $listOfNonExportedInterfaces */" else ""
|
||||||
|
" $superInterfacesKeyword " + exportedInterfaces.joinToString(", ") { it.toTypeScript(indent) } + nonExportedInterfacesTsString
|
||||||
|
}
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ExportedRegularClass.shouldNotBeImplemented(): Boolean {
|
||||||
|
return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ExportedDeclaration>.withMagicProperty(): List<ExportedDeclaration> {
|
||||||
|
return plus(
|
||||||
|
ExportedProperty(
|
||||||
|
"__doNotUseIt",
|
||||||
|
ExportedType.TypeParameter(doNotImplementIt),
|
||||||
|
mutable = false,
|
||||||
|
isMember = true,
|
||||||
|
isStatic = false,
|
||||||
|
isAbstract = false,
|
||||||
|
isProtected = false,
|
||||||
|
isField = true,
|
||||||
|
irGetter = null,
|
||||||
|
irSetter = null,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
|
private fun IrClass.asNestedClassAccess(): String {
|
||||||
joinToString("") { it.toTypeScript(indent) + "\n" }
|
val name = getJsNameOrKotlinName().identifier
|
||||||
|
if (parent !is IrClass) return name
|
||||||
|
return "${parentAsClass.asNestedClassAccess()}.$name"
|
||||||
|
}
|
||||||
|
|
||||||
fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
|
private fun ExportedClass.withProtectedConstructors(): ExportedRegularClass {
|
||||||
indent + when (this) {
|
return (this as ExportedRegularClass).copy(members = members.map {
|
||||||
is ErrorDeclaration -> "/* ErrorDeclaration: $message */"
|
if (it !is ExportedConstructor || it.isProtected) {
|
||||||
|
it
|
||||||
is ExportedNamespace ->
|
|
||||||
"${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}"
|
|
||||||
|
|
||||||
is ExportedFunction -> {
|
|
||||||
val visibility = if (isProtected) "protected " else ""
|
|
||||||
|
|
||||||
val keyword: String = when {
|
|
||||||
isMember -> when {
|
|
||||||
isStatic -> "static "
|
|
||||||
isAbstract -> "abstract "
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
else -> "function "
|
|
||||||
}
|
|
||||||
|
|
||||||
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
|
||||||
|
|
||||||
val renderedTypeParameters =
|
|
||||||
if (typeParameters.isNotEmpty())
|
|
||||||
"<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">"
|
|
||||||
else
|
|
||||||
""
|
|
||||||
|
|
||||||
val renderedReturnType = returnType.toTypeScript(indent)
|
|
||||||
val containsUnresolvedChar = !name.isValidES5Identifier()
|
|
||||||
|
|
||||||
val escapedName = when {
|
|
||||||
isMember && containsUnresolvedChar -> "\"$name\""
|
|
||||||
else -> name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMember && containsUnresolvedChar) "" else "${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
|
|
||||||
}
|
|
||||||
|
|
||||||
is ExportedConstructor -> {
|
|
||||||
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
|
||||||
"${visibility.keyword}constructor($renderedParameters);"
|
|
||||||
}
|
|
||||||
|
|
||||||
is ExportedConstructSignature -> {
|
|
||||||
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
|
|
||||||
"new($renderedParameters): ${returnType.toTypeScript(indent)};"
|
|
||||||
}
|
|
||||||
|
|
||||||
is ExportedProperty -> {
|
|
||||||
val visibility = if (isProtected) "protected " else ""
|
|
||||||
val keyword = when {
|
|
||||||
isMember -> (if (isAbstract) "abstract " else "")
|
|
||||||
else -> if (mutable) "let " else "const "
|
|
||||||
}
|
|
||||||
val possibleStatic = if (isMember && isStatic) "static " else ""
|
|
||||||
val containsUnresolvedChar = !name.isValidES5Identifier()
|
|
||||||
val memberName = when {
|
|
||||||
isMember && containsUnresolvedChar -> "\"$name\""
|
|
||||||
else -> name
|
|
||||||
}
|
|
||||||
val typeToTypeScript = type.toTypeScript(indent)
|
|
||||||
if (isMember && !isField) {
|
|
||||||
val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;"
|
|
||||||
if (!mutable) getter
|
|
||||||
else getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);"
|
|
||||||
} else {
|
} else {
|
||||||
if (!isMember && containsUnresolvedChar) ""
|
it.copy(visibility = ExportedVisibility.PROTECTED)
|
||||||
else {
|
|
||||||
val readonly = if (isMember && !mutable) "readonly " else ""
|
|
||||||
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
is ExportedClass -> {
|
|
||||||
val keyword = if (isInterface) "interface" else "class"
|
|
||||||
val superInterfacesKeyword = if (isInterface) "extends" else "implements"
|
|
||||||
|
|
||||||
val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: ""
|
|
||||||
val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent)
|
|
||||||
|
|
||||||
val members = members
|
|
||||||
.let { if (shouldNotBeImplemented()) it.withMagicProperty() else it }
|
|
||||||
.map {
|
|
||||||
if (!ir.isInner || it !is ExportedFunction || !it.isStatic) {
|
|
||||||
it
|
|
||||||
} else {
|
|
||||||
// Remove $outer argument from secondary constructors of inner classes
|
|
||||||
it.copy(parameters = it.parameters.drop(1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner }
|
|
||||||
val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
|
|
||||||
val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" }
|
|
||||||
|
|
||||||
// If there are no exported constructors, add a private constructor to disable default one
|
|
||||||
val privateCtorString =
|
|
||||||
if (!isInterface && !isAbstract && members.none { it is ExportedConstructor })
|
|
||||||
"$indent private constructor();\n"
|
|
||||||
else
|
|
||||||
""
|
|
||||||
|
|
||||||
val renderedTypeParameters =
|
|
||||||
if (typeParameters.isNotEmpty())
|
|
||||||
"<" + typeParameters.joinToString(", ") + ">"
|
|
||||||
else
|
|
||||||
""
|
|
||||||
|
|
||||||
val modifiers = if (isAbstract && !isInterface) "abstract " else ""
|
|
||||||
|
|
||||||
val bodyString = privateCtorString + membersString + indent
|
|
||||||
|
|
||||||
val nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() }
|
|
||||||
val klassExport =
|
|
||||||
"$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
|
|
||||||
val staticsExport =
|
|
||||||
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
|
|
||||||
|
|
||||||
if (name.isValidES5Identifier()) klassExport + staticsExport else ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExportedType.toExtendsClause(indent: String): String {
|
private fun ExportedClass.toReadonlyProperty(): ExportedProperty {
|
||||||
val isImplicitlyExportedType = this is ExportedType.ImplicitlyExportedType
|
val innerClassReference = ir.asNestedClassAccess()
|
||||||
val extendsClause = " extends ${toTypeScript(indent, isImplicitlyExportedType)}"
|
val allPublicConstructors = members.asSequence()
|
||||||
return when {
|
.filterIsInstance<ExportedConstructor>()
|
||||||
isImplicitlyExportedType -> " /*$extendsClause */"
|
.filterNot { it.isProtected }
|
||||||
else -> extendsClause
|
.map {
|
||||||
}
|
ExportedConstructSignature(
|
||||||
}
|
parameters = it.parameters.drop(1),
|
||||||
|
returnType = ExportedType.TypeParameter(innerClassReference),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent: String): String {
|
val type = ExportedType.IntersectionType(
|
||||||
val (exportedInterfaces, nonExportedInterfaces) = partition { it !is ExportedType.ImplicitlyExportedType }
|
ExportedType.InlineInterfaceType(allPublicConstructors),
|
||||||
val listOfNonExportedInterfaces = nonExportedInterfaces.joinToString(", ") {
|
ExportedType.TypeOf(innerClassReference)
|
||||||
(it as ExportedType.ImplicitlyExportedType).type.toTypeScript(indent, true)
|
)
|
||||||
}
|
|
||||||
return when {
|
|
||||||
exportedInterfaces.isEmpty() && nonExportedInterfaces.isNotEmpty() ->
|
|
||||||
" /* $superInterfacesKeyword $listOfNonExportedInterfaces */"
|
|
||||||
exportedInterfaces.isNotEmpty() -> {
|
|
||||||
val nonExportedInterfacesTsString = if (nonExportedInterfaces.isNotEmpty()) "/*, $listOfNonExportedInterfaces */" else ""
|
|
||||||
" $superInterfacesKeyword " + exportedInterfaces.joinToString(", ") { it.toTypeScript(indent) } + nonExportedInterfacesTsString
|
|
||||||
}
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ExportedClass.shouldNotBeImplemented(): Boolean {
|
return ExportedProperty(
|
||||||
return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal }
|
name = name,
|
||||||
}
|
type = type,
|
||||||
|
|
||||||
fun List<ExportedDeclaration>.withMagicProperty(): List<ExportedDeclaration> {
|
|
||||||
return plus(
|
|
||||||
ExportedProperty(
|
|
||||||
"__doNotUseIt",
|
|
||||||
ExportedType.TypeParameter("__doNotImplementIt"),
|
|
||||||
mutable = false,
|
mutable = false,
|
||||||
isMember = true,
|
isMember = true,
|
||||||
isStatic = false,
|
isStatic = false,
|
||||||
isAbstract = false,
|
isAbstract = false,
|
||||||
isProtected = false,
|
isProtected = false,
|
||||||
isField = true,
|
isField = false,
|
||||||
irGetter = null,
|
irGetter = null,
|
||||||
irSetter = null,
|
irSetter = null
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun IrClass.asNestedClassAccess(): String {
|
private fun ExportedParameter.toTypeScript(indent: String): String {
|
||||||
val name = getJsNameOrKotlinName().identifier
|
val name = sanitizeName(name, withHash = false)
|
||||||
if (parent !is IrClass) return name
|
val type = type.toTypeScript(indent)
|
||||||
return "${parentAsClass.asNestedClassAccess()}.$name"
|
val questionMark = if (hasDefaultValue) "?" else ""
|
||||||
}
|
return "$name$questionMark: $type"
|
||||||
|
}
|
||||||
|
|
||||||
fun ExportedClass.withProtectedConstructors(): ExportedClass {
|
private fun ExportedType.toTypeScript(indent: String, isInCommentContext: Boolean = false): String = when (this) {
|
||||||
return copy(members = members.map {
|
is ExportedType.Primitive -> typescript
|
||||||
if (it !is ExportedConstructor || it.isProtected) {
|
is ExportedType.Array -> "Array<${elementType.toTypeScript(indent, isInCommentContext)}>"
|
||||||
it
|
is ExportedType.Function -> "(" + parameterTypes
|
||||||
|
.withIndex()
|
||||||
|
.joinToString(", ") { (index, type) ->
|
||||||
|
"p$index: ${type.toTypeScript(indent, isInCommentContext)}"
|
||||||
|
} + ") => " + returnType.toTypeScript(indent, isInCommentContext)
|
||||||
|
|
||||||
|
is ExportedType.ClassType ->
|
||||||
|
name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent, isInCommentContext) }}>" else ""
|
||||||
|
is ExportedType.TypeOf ->
|
||||||
|
"typeof $name"
|
||||||
|
|
||||||
|
is ExportedType.ErrorType -> if (isInCommentContext) comment else "any /*$comment*/"
|
||||||
|
is ExportedType.Nullable -> "$Nullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
|
||||||
|
is ExportedType.InlineInterfaceType -> {
|
||||||
|
members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
|
||||||
|
}
|
||||||
|
is ExportedType.IntersectionType -> {
|
||||||
|
lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent, isInCommentContext)
|
||||||
|
}
|
||||||
|
is ExportedType.UnionType -> {
|
||||||
|
lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent, isInCommentContext)
|
||||||
|
}
|
||||||
|
is ExportedType.LiteralType.StringLiteralType -> "\"$value\""
|
||||||
|
is ExportedType.LiteralType.NumberLiteralType -> value.toString()
|
||||||
|
is ExportedType.ImplicitlyExportedType -> {
|
||||||
|
val typeString = type.toTypeScript("", true)
|
||||||
|
if (isInCommentContext) typeString else ExportedType.Primitive.Any.toTypeScript(indent) + "/* $typeString */"
|
||||||
|
}
|
||||||
|
is ExportedType.TypeParameter -> if (constraint == null) {
|
||||||
|
name
|
||||||
} else {
|
} else {
|
||||||
it.copy(visibility = ExportedVisibility.PROTECTED)
|
"$name extends ${constraint.toTypeScript(indent, isInCommentContext)}"
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun ExportedClass.toReadonlyProperty(): ExportedProperty {
|
private fun ExportedClass.couldBeProperty(): Boolean {
|
||||||
val innerClassReference = ir.asNestedClassAccess()
|
return this is ExportedObject && nestedClasses.all {
|
||||||
val allPublicConstructors = members.asSequence()
|
it.couldBeProperty() && it.ir.visibility != DescriptorVisibilities.PROTECTED
|
||||||
.filterIsInstance<ExportedConstructor>()
|
|
||||||
.filterNot { it.isProtected }
|
|
||||||
.map {
|
|
||||||
ExportedConstructSignature(
|
|
||||||
parameters = it.parameters.drop(1),
|
|
||||||
returnType = ExportedType.TypeParameter(innerClassReference),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.toList()
|
|
||||||
|
|
||||||
val type = ExportedType.IntersectionType(
|
|
||||||
ExportedType.InlineInterfaceType(allPublicConstructors),
|
|
||||||
ExportedType.TypeOf(innerClassReference)
|
|
||||||
)
|
|
||||||
|
|
||||||
return ExportedProperty(
|
|
||||||
name = name,
|
|
||||||
type = type,
|
|
||||||
mutable = false,
|
|
||||||
isMember = true,
|
|
||||||
isStatic = false,
|
|
||||||
isAbstract = false,
|
|
||||||
isProtected = false,
|
|
||||||
isField = false,
|
|
||||||
irGetter = null,
|
|
||||||
irSetter = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ExportedParameter.toTypeScript(indent: String): String {
|
|
||||||
val name = sanitizeName(name, withHash = false)
|
|
||||||
val type = type.toTypeScript(indent)
|
|
||||||
val questionMark = if (hasDefaultValue) "?" else ""
|
|
||||||
return "$name$questionMark: $type"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ExportedType.toTypeScript(indent: String, isInCommentContext: Boolean = false): String = when (this) {
|
|
||||||
is ExportedType.Primitive -> typescript
|
|
||||||
is ExportedType.Array -> "Array<${elementType.toTypeScript(indent, isInCommentContext)}>"
|
|
||||||
is ExportedType.Function -> "(" + parameterTypes
|
|
||||||
.withIndex()
|
|
||||||
.joinToString(", ") { (index, type) ->
|
|
||||||
"p$index: ${type.toTypeScript(indent, isInCommentContext)}"
|
|
||||||
} + ") => " + returnType.toTypeScript(indent, isInCommentContext)
|
|
||||||
|
|
||||||
is ExportedType.ClassType ->
|
|
||||||
name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent, isInCommentContext) }}>" else ""
|
|
||||||
is ExportedType.TypeOf ->
|
|
||||||
"typeof $name"
|
|
||||||
|
|
||||||
is ExportedType.ErrorType -> if (isInCommentContext) comment else "any /*$comment*/"
|
|
||||||
is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
|
|
||||||
is ExportedType.InlineInterfaceType -> {
|
|
||||||
members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
|
|
||||||
}
|
|
||||||
is ExportedType.IntersectionType -> {
|
|
||||||
lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent, isInCommentContext)
|
|
||||||
}
|
|
||||||
is ExportedType.UnionType -> {
|
|
||||||
lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent, isInCommentContext)
|
|
||||||
}
|
|
||||||
is ExportedType.LiteralType.StringLiteralType -> "\"$value\""
|
|
||||||
is ExportedType.LiteralType.NumberLiteralType -> value.toString()
|
|
||||||
is ExportedType.ImplicitlyExportedType -> {
|
|
||||||
val typeString = type.toTypeScript("", true)
|
|
||||||
if (isInCommentContext) typeString else ExportedType.Primitive.Any.toTypeScript(indent) + "/* $typeString */"
|
|
||||||
}
|
|
||||||
is ExportedType.TypeParameter -> if (constraint == null) {
|
|
||||||
name
|
|
||||||
} else {
|
|
||||||
"$name extends ${constraint.toTypeScript(indent, isInCommentContext)}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -86,7 +86,7 @@ class IrModuleToJsTransformerTmp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val dts = wrapTypeScript(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatMap { it } }.toTypeScript(moduleKind))
|
val dts = ExportedModule(mainModuleName, moduleKind, exportData.values.flatMap { it.values.flatten() }).toTypeScript()
|
||||||
|
|
||||||
modules.forEach { module ->
|
modules.forEach { module ->
|
||||||
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
|
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
|
||||||
|
|||||||
@@ -198,5 +198,39 @@ declare namespace JS_TESTS {
|
|||||||
hashCode(): number;
|
hashCode(): number;
|
||||||
equals(other: Nullable<any>): boolean;
|
equals(other: Nullable<any>): boolean;
|
||||||
}
|
}
|
||||||
|
abstract class Parent {
|
||||||
|
private constructor();
|
||||||
|
}
|
||||||
|
namespace Parent {
|
||||||
|
abstract class Nested1 extends _objects_.foo$Parent$Nested1 {
|
||||||
|
private constructor();
|
||||||
|
}
|
||||||
|
namespace Nested1 {
|
||||||
|
class Nested2 {
|
||||||
|
constructor();
|
||||||
|
}
|
||||||
|
namespace Nested2 {
|
||||||
|
abstract class Companion {
|
||||||
|
private constructor();
|
||||||
|
}
|
||||||
|
namespace Companion {
|
||||||
|
class Nested3 {
|
||||||
|
constructor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getParent(): typeof foo.Parent;
|
||||||
|
function createNested1(): typeof foo.Parent.Nested1;
|
||||||
|
function createNested2(): foo.Parent.Nested1.Nested2;
|
||||||
|
function createNested3(): foo.Parent.Nested1.Nested2.Companion.Nested3;
|
||||||
|
}
|
||||||
|
namespace _objects_ {
|
||||||
|
const foo$Parent$Nested1: {
|
||||||
|
get value(): string;
|
||||||
|
} & {
|
||||||
|
new(): any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,3 +257,35 @@ data class KT39423(
|
|||||||
val a: String,
|
val a: String,
|
||||||
val b: Int? = null
|
val b: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
object Parent {
|
||||||
|
object Nested1 {
|
||||||
|
val value: String = "Nested1"
|
||||||
|
class Nested2 {
|
||||||
|
companion object {
|
||||||
|
class Nested3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun getParent(): Parent {
|
||||||
|
return Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun createNested1(): Parent.Nested1 {
|
||||||
|
return Parent.Nested1
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun createNested2(): Parent.Nested1.Nested2 {
|
||||||
|
return Parent.Nested1.Nested2()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun createNested3(): Parent.Nested1.Nested2.Companion.Nested3 {
|
||||||
|
return Parent.Nested1.Nested2.Companion.Nested3()
|
||||||
|
}
|
||||||
|
|||||||
+15
@@ -32,6 +32,11 @@ var processInterface = JS_TESTS.foo.processInterface;
|
|||||||
var OuterClass = JS_TESTS.foo.OuterClass;
|
var OuterClass = JS_TESTS.foo.OuterClass;
|
||||||
var KT38262 = JS_TESTS.foo.KT38262;
|
var KT38262 = JS_TESTS.foo.KT38262;
|
||||||
var JsNameTest = JS_TESTS.foo.JsNameTest;
|
var JsNameTest = JS_TESTS.foo.JsNameTest;
|
||||||
|
var Parent = JS_TESTS.foo.Parent;
|
||||||
|
var getParent = JS_TESTS.foo.getParent;
|
||||||
|
var createNested1 = JS_TESTS.foo.createNested1;
|
||||||
|
var createNested2 = JS_TESTS.foo.createNested2;
|
||||||
|
var createNested3 = JS_TESTS.foo.createNested3;
|
||||||
function assert(condition) {
|
function assert(condition) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw "Assertion failed";
|
throw "Assertion failed";
|
||||||
@@ -135,5 +140,15 @@ function box() {
|
|||||||
assert(jsNameTest.runTest() === "JsNameTest");
|
assert(jsNameTest.runTest() === "JsNameTest");
|
||||||
var jsNameNestedTest = JsNameTest.Companion.createChild(42);
|
var jsNameNestedTest = JsNameTest.Companion.createChild(42);
|
||||||
assert(jsNameNestedTest.value === 42);
|
assert(jsNameNestedTest.value === 42);
|
||||||
|
// Do not strip types from those test cases (it is a check of nested objects types usability)
|
||||||
|
var parent = Parent;
|
||||||
|
var nested1 = Parent.Nested1;
|
||||||
|
var nested2 = new Parent.Nested1.Nested2();
|
||||||
|
var nested3 = new Parent.Nested1.Nested2.Companion.Nested3();
|
||||||
|
assert(nested1.value === "Nested1");
|
||||||
|
assert(getParent() === parent);
|
||||||
|
assert(createNested1() === nested1);
|
||||||
|
assert(createNested2() !== nested2);
|
||||||
|
assert(createNested3() !== nested3);
|
||||||
return "OK";
|
return "OK";
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -32,6 +32,11 @@ import processInterface = JS_TESTS.foo.processInterface;
|
|||||||
import OuterClass = JS_TESTS.foo.OuterClass;
|
import OuterClass = JS_TESTS.foo.OuterClass;
|
||||||
import KT38262 = JS_TESTS.foo.KT38262;
|
import KT38262 = JS_TESTS.foo.KT38262;
|
||||||
import JsNameTest = JS_TESTS.foo.JsNameTest;
|
import JsNameTest = JS_TESTS.foo.JsNameTest;
|
||||||
|
import Parent = JS_TESTS.foo.Parent;
|
||||||
|
import getParent = JS_TESTS.foo.getParent;
|
||||||
|
import createNested1 = JS_TESTS.foo.createNested1;
|
||||||
|
import createNested2 = JS_TESTS.foo.createNested2;
|
||||||
|
import createNested3 = JS_TESTS.foo.createNested3;
|
||||||
|
|
||||||
function assert(condition: boolean) {
|
function assert(condition: boolean) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
@@ -166,5 +171,16 @@ function box(): string {
|
|||||||
|
|
||||||
assert(jsNameNestedTest.value === 42)
|
assert(jsNameNestedTest.value === 42)
|
||||||
|
|
||||||
|
// Do not strip types from those test cases (it is a check of nested objects types usability)
|
||||||
|
const parent: typeof Parent = Parent
|
||||||
|
const nested1: typeof Parent.Nested1 = Parent.Nested1
|
||||||
|
const nested2: Parent.Nested1.Nested2 = new Parent.Nested1.Nested2()
|
||||||
|
const nested3: Parent.Nested1.Nested2.Companion.Nested3 = new Parent.Nested1.Nested2.Companion.Nested3()
|
||||||
|
|
||||||
|
assert(nested1.value === "Nested1")
|
||||||
|
assert(getParent() === parent)
|
||||||
|
assert(createNested1() === nested1)
|
||||||
|
assert(createNested2() !== nested2)
|
||||||
|
assert(createNested3() !== nested3)
|
||||||
return "OK";
|
return "OK";
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,13 @@ declare namespace JS_TESTS {
|
|||||||
constructor();
|
constructor();
|
||||||
protected get protectedVal(): number;
|
protected get protectedVal(): number;
|
||||||
protected protectedFun(): number;
|
protected protectedFun(): number;
|
||||||
|
get publicVal(): number;
|
||||||
|
publicFun(): number;
|
||||||
protected static get protectedNestedObject(): {
|
protected static get protectedNestedObject(): {
|
||||||
};
|
};
|
||||||
protected static get Companion(): {
|
protected static get Companion(): {
|
||||||
get companionObjectProp(): number;
|
get companionObjectProp(): number;
|
||||||
};
|
};
|
||||||
get publicVal(): number;
|
|
||||||
publicFun(): number;
|
|
||||||
}
|
}
|
||||||
namespace Class {
|
namespace Class {
|
||||||
class protectedClass {
|
class protectedClass {
|
||||||
|
|||||||
Reference in New Issue
Block a user