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(
|
||||
val name: String,
|
||||
val declarations: List<ExportedDeclaration>
|
||||
val declarations: List<ExportedDeclaration>,
|
||||
) : ExportedDeclaration()
|
||||
|
||||
data class ExportedFunction(
|
||||
@@ -59,24 +59,43 @@ class ExportedProperty(
|
||||
val isField: Boolean,
|
||||
val irGetter: IrFunction?,
|
||||
val irSetter: IrFunction?,
|
||||
val exportedObject: ExportedClass? = null,
|
||||
) : ExportedDeclaration()
|
||||
|
||||
|
||||
// TODO: Cover all cases with frontend and disable error declarations
|
||||
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 isAbstract: Boolean = false,
|
||||
val superClass: ExportedType? = null,
|
||||
val superInterfaces: List<ExportedType> = emptyList(),
|
||||
override val superClass: ExportedType? = null,
|
||||
override val superInterfaces: List<ExportedType> = emptyList(),
|
||||
val typeParameters: List<String>,
|
||||
val members: List<ExportedDeclaration>,
|
||||
val nestedClasses: List<ExportedClass>,
|
||||
val ir: IrClass
|
||||
) : ExportedDeclaration()
|
||||
override val members: List<ExportedDeclaration>,
|
||||
override val nestedClasses: List<ExportedClass>,
|
||||
override val ir: IrClass,
|
||||
) : 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(
|
||||
val name: String,
|
||||
|
||||
+21
-37
@@ -270,9 +270,7 @@ class ExportModelGenerator(
|
||||
listOf(privateConstructor) + members,
|
||||
nestedClasses
|
||||
).let {
|
||||
(it as ExportedClass).copy(
|
||||
isAbstract = true,
|
||||
)
|
||||
(it as ExportedRegularClass).copy(isAbstract = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,43 +350,29 @@ class ExportModelGenerator(
|
||||
|
||||
val name = klass.getExportedIdentifier()
|
||||
|
||||
val exportedClass = ExportedClass(
|
||||
name = name,
|
||||
isInterface = klass.isInterface,
|
||||
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(
|
||||
return if (klass.kind == ClassKind.OBJECT) {
|
||||
return ExportedObject(
|
||||
ir = klass,
|
||||
name = name,
|
||||
type = t,
|
||||
mutable = false,
|
||||
isMember = klass.parent is IrClass,
|
||||
isStatic = !klass.isInner,
|
||||
isAbstract = false,
|
||||
isProtected = klass.visibility == DescriptorVisibilities.PROTECTED,
|
||||
irGetter = context.mapping.objectToGetInstanceFunction[klass]!!,
|
||||
irSetter = null,
|
||||
exportedObject = exportedClass,
|
||||
isField = false,
|
||||
members = members,
|
||||
superClass = superType,
|
||||
nestedClasses = nestedClasses,
|
||||
superInterfaces = superInterfaces,
|
||||
irGetter = context.mapping.objectToGetInstanceFunction[klass]!!
|
||||
)
|
||||
} else {
|
||||
ExportedRegularClass(
|
||||
name = name,
|
||||
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(
|
||||
|
||||
+12
-10
@@ -86,17 +86,22 @@ class ExportModelToJsStatements(
|
||||
|
||||
is ExportedProperty -> {
|
||||
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 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 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()
|
||||
val newNameSpace = if (namespace != null)
|
||||
jsElementAccess(declaration.name, namespace)
|
||||
@@ -120,16 +125,13 @@ class ExportModelToJsStatements(
|
||||
.filter { it is ExportedFunction && it.isStatic }
|
||||
.takeIf { !declaration.ir.isInner }.orEmpty()
|
||||
|
||||
// Nested objects are exported as static properties
|
||||
val staticProperties = declaration.members.mapNotNull {
|
||||
(it as? ExportedProperty)?.takeIf { it.isStatic }
|
||||
}
|
||||
val enumEntries = declaration.members.filter { it is ExportedProperty && it.isStatic }
|
||||
|
||||
val innerClassesAssignments = declaration.nestedClasses
|
||||
.filter { it.ir.isInner }
|
||||
.map { it.generateInnerClassAssignment(declaration) }
|
||||
|
||||
val staticsExport = (staticFunctions + staticProperties + declaration.nestedClasses)
|
||||
val staticsExport = (staticFunctions + enumEntries + declaration.nestedClasses)
|
||||
.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
|
||||
|
||||
listOfNotNull(klassExport) + staticsExport + innerClassesAssignments
|
||||
|
||||
+395
-275
@@ -5,314 +5,434 @@
|
||||
|
||||
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.sanitizeName
|
||||
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.js.common.isValidES5Identifier
|
||||
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 {
|
||||
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 {
|
||||
return joinToString("\n") {
|
||||
it.toTypeScript(
|
||||
indent = moduleKind.indent,
|
||||
prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export "
|
||||
return ExportModelToTsDeclarations().generateTypeScript(moduleKind, this)
|
||||
}
|
||||
|
||||
// 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 =
|
||||
joinToString("") { it.toTypeScript(indent) + "\n" }
|
||||
private fun IrClass.asNestedClassAccess(): String {
|
||||
val name = getJsNameOrKotlinName().identifier
|
||||
if (parent !is IrClass) return name
|
||||
return "${parentAsClass.asNestedClassAccess()}.$name"
|
||||
}
|
||||
|
||||
fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
|
||||
indent + when (this) {
|
||||
is ErrorDeclaration -> "/* ErrorDeclaration: $message */"
|
||||
|
||||
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);"
|
||||
private fun ExportedClass.withProtectedConstructors(): ExportedRegularClass {
|
||||
return (this as ExportedRegularClass).copy(members = members.map {
|
||||
if (it !is ExportedConstructor || it.isProtected) {
|
||||
it
|
||||
} else {
|
||||
if (!isMember && containsUnresolvedChar) ""
|
||||
else {
|
||||
val readonly = if (isMember && !mutable) "readonly " else ""
|
||||
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
|
||||
}
|
||||
it.copy(visibility = ExportedVisibility.PROTECTED)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
val isImplicitlyExportedType = this is ExportedType.ImplicitlyExportedType
|
||||
val extendsClause = " extends ${toTypeScript(indent, isImplicitlyExportedType)}"
|
||||
return when {
|
||||
isImplicitlyExportedType -> " /*$extendsClause */"
|
||||
else -> extendsClause
|
||||
}
|
||||
}
|
||||
private fun ExportedClass.toReadonlyProperty(): ExportedProperty {
|
||||
val innerClassReference = ir.asNestedClassAccess()
|
||||
val allPublicConstructors = members.asSequence()
|
||||
.filterIsInstance<ExportedConstructor>()
|
||||
.filterNot { it.isProtected }
|
||||
.map {
|
||||
ExportedConstructSignature(
|
||||
parameters = it.parameters.drop(1),
|
||||
returnType = ExportedType.TypeParameter(innerClassReference),
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
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 -> ""
|
||||
}
|
||||
}
|
||||
val type = ExportedType.IntersectionType(
|
||||
ExportedType.InlineInterfaceType(allPublicConstructors),
|
||||
ExportedType.TypeOf(innerClassReference)
|
||||
)
|
||||
|
||||
fun ExportedClass.shouldNotBeImplemented(): Boolean {
|
||||
return (isInterface && !ir.isExternal) || superInterfaces.any { it is ExportedType.ClassType && !it.ir.isExternal }
|
||||
}
|
||||
|
||||
fun List<ExportedDeclaration>.withMagicProperty(): List<ExportedDeclaration> {
|
||||
return plus(
|
||||
ExportedProperty(
|
||||
"__doNotUseIt",
|
||||
ExportedType.TypeParameter("__doNotImplementIt"),
|
||||
return ExportedProperty(
|
||||
name = name,
|
||||
type = type,
|
||||
mutable = false,
|
||||
isMember = true,
|
||||
isStatic = false,
|
||||
isAbstract = false,
|
||||
isProtected = false,
|
||||
isField = true,
|
||||
isField = false,
|
||||
irGetter = null,
|
||||
irSetter = null,
|
||||
irSetter = null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun IrClass.asNestedClassAccess(): String {
|
||||
val name = getJsNameOrKotlinName().identifier
|
||||
if (parent !is IrClass) return name
|
||||
return "${parentAsClass.asNestedClassAccess()}.$name"
|
||||
}
|
||||
private 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 ExportedClass.withProtectedConstructors(): ExportedClass {
|
||||
return copy(members = members.map {
|
||||
if (it !is ExportedConstructor || it.isProtected) {
|
||||
it
|
||||
private 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 {
|
||||
it.copy(visibility = ExportedVisibility.PROTECTED)
|
||||
"$name extends ${constraint.toTypeScript(indent, isInCommentContext)}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun ExportedClass.toReadonlyProperty(): ExportedProperty {
|
||||
val innerClassReference = ir.asNestedClassAccess()
|
||||
val allPublicConstructors = members.asSequence()
|
||||
.filterIsInstance<ExportedConstructor>()
|
||||
.filterNot { it.isProtected }
|
||||
.map {
|
||||
ExportedConstructSignature(
|
||||
parameters = it.parameters.drop(1),
|
||||
returnType = ExportedType.TypeParameter(innerClassReference),
|
||||
)
|
||||
private fun ExportedClass.couldBeProperty(): Boolean {
|
||||
return this is ExportedObject && nestedClasses.all {
|
||||
it.couldBeProperty() && it.ir.visibility != DescriptorVisibilities.PROTECTED
|
||||
}
|
||||
.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 ->
|
||||
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
|
||||
|
||||
+35
-1
@@ -198,5 +198,39 @@ declare namespace JS_TESTS {
|
||||
hashCode(): number;
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -256,4 +256,36 @@ class __JsNameTest private constructor() {
|
||||
data class KT39423(
|
||||
val a: String,
|
||||
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 KT38262 = JS_TESTS.foo.KT38262;
|
||||
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) {
|
||||
if (!condition) {
|
||||
throw "Assertion failed";
|
||||
@@ -135,5 +140,15 @@ function box() {
|
||||
assert(jsNameTest.runTest() === "JsNameTest");
|
||||
var jsNameNestedTest = JsNameTest.Companion.createChild(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";
|
||||
}
|
||||
|
||||
+16
@@ -32,6 +32,11 @@ import processInterface = JS_TESTS.foo.processInterface;
|
||||
import OuterClass = JS_TESTS.foo.OuterClass;
|
||||
import KT38262 = JS_TESTS.foo.KT38262;
|
||||
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) {
|
||||
if (!condition) {
|
||||
@@ -166,5 +171,16 @@ function box(): string {
|
||||
|
||||
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";
|
||||
}
|
||||
@@ -13,13 +13,13 @@ declare namespace JS_TESTS {
|
||||
constructor();
|
||||
protected get protectedVal(): number;
|
||||
protected protectedFun(): number;
|
||||
get publicVal(): number;
|
||||
publicFun(): number;
|
||||
protected static get protectedNestedObject(): {
|
||||
};
|
||||
protected static get Companion(): {
|
||||
get companionObjectProp(): number;
|
||||
};
|
||||
get publicVal(): number;
|
||||
publicFun(): number;
|
||||
}
|
||||
namespace Class {
|
||||
class protectedClass {
|
||||
|
||||
Reference in New Issue
Block a user