fix(Kotlin/JS IR): nested comments in implicit export.

This commit is contained in:
Artem Kobzar
2022-02-04 10:55:52 +00:00
committed by Space
parent a80d01265a
commit 5223efd3f1
3 changed files with 132 additions and 113 deletions
@@ -55,138 +55,142 @@ fun List<ExportedDeclaration>.toTypeScript(moduleKind: ModuleKind): String {
fun List<ExportedDeclaration>.toTypeScript(indent: String): String = fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
joinToString("") { it.toTypeScript(indent) + "\n" } joinToString("") { it.toTypeScript(indent) + "\n" }
fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String = indent + when (this) { fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
is ErrorDeclaration -> "/* ErrorDeclaration: $message */" indent + when (this) {
is ErrorDeclaration -> "/* ErrorDeclaration: $message */"
is ExportedNamespace -> is ExportedNamespace ->
"${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}" "${prefix}namespace $name {\n" + declarations.toTypeScript("$indent ") + "$indent}"
is ExportedFunction -> { is ExportedFunction -> {
val visibility = if (isProtected) "protected " else "" val visibility = if (isProtected) "protected " else ""
val keyword: String = when { val keyword: String = when {
isMember -> when { isMember -> when {
isStatic -> "static " isStatic -> "static "
isAbstract -> "abstract " isAbstract -> "abstract "
else -> "" else -> ""
}
else -> "function "
} }
else -> "function "
}
val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) } val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
val renderedTypeParameters = val renderedTypeParameters =
if (typeParameters.isNotEmpty()) if (typeParameters.isNotEmpty())
"<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">" "<" + typeParameters.joinToString(", ") { it.toTypeScript(indent) } + ">"
else else
"" ""
val renderedReturnType = returnType.toTypeScript(indent) val renderedReturnType = returnType.toTypeScript(indent)
val containsUnresolvedChar = !name.isValidES5Identifier() val containsUnresolvedChar = !name.isValidES5Identifier()
val escapedName = when { val escapedName = when {
isMember && containsUnresolvedChar -> "\"$name\"" isMember && containsUnresolvedChar -> "\"$name\""
else -> 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 {
if (!isMember && containsUnresolvedChar) ""
else {
val readonly = if (isMember && !mutable) "readonly " else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
} }
if (!isMember && containsUnresolvedChar) "" else "${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
} }
}
is ExportedClass -> { is ExportedConstructor -> {
val keyword = if (isInterface) "interface" else "class" val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
val superInterfacesKeyword = if (isInterface) "extends" else "implements" "${visibility.keyword}constructor($renderedParameters);"
}
val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: "" is ExportedConstructSignature -> {
val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent) val renderedParameters = parameters.joinToString(", ") { it.toTypeScript(indent) }
"new($renderedParameters): ${returnType.toTypeScript(indent)};"
}
val members = members is ExportedProperty -> {
.let { if (shouldNotBeImplemented()) it.withMagicProperty() else it } val visibility = if (isProtected) "protected " else ""
.map { val keyword = when {
if (!ir.isInner || it !is ExportedFunction || !it.isStatic) { isMember -> (if (isAbstract) "abstract " else "")
it else -> if (mutable) "let " else "const "
} else { }
// Remove $outer argument from secondary constructors of inner classes val possibleStatic = if (isMember && isStatic) "static " else ""
it.copy(parameters = it.parameters.drop(1)) 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 {
if (!isMember && containsUnresolvedChar) ""
else {
val readonly = if (isMember && !mutable) "readonly " else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName: $typeToTypeScript;"
} }
} }
}
val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner } is ExportedClass -> {
val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() } val keyword = if (isInterface) "interface" else "class"
val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" } val superInterfacesKeyword = if (isInterface) "extends" else "implements"
// If there are no exported constructors, add a private constructor to disable default one val superClassClause = superClass?.let { it.toExtendsClause(indent) } ?: ""
val privateCtorString = val superInterfacesClause = superInterfaces.toImplementsClause(superInterfacesKeyword, indent)
if (!isInterface && !isAbstract && members.none { it is ExportedConstructor })
"$indent private constructor();\n"
else
""
val renderedTypeParameters = val members = members
if (typeParameters.isNotEmpty()) .let { if (shouldNotBeImplemented()) it.withMagicProperty() else it }
"<" + typeParameters.joinToString(", ") + ">" .map {
else 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 modifiers = if (isAbstract && !isInterface) "abstract " else "" val (innerClasses, nonInnerClasses) = nestedClasses.partition { it.ir.isInner }
val innerClassesProperties = innerClasses.map { it.toReadonlyProperty() }
val membersString = (members + innerClassesProperties).joinToString("") { it.toTypeScript("$indent ") + "\n" }
val bodyString = privateCtorString + membersString + indent // 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 nestedClasses = nonInnerClasses + innerClasses.map { it.withProtectedConstructors() } val renderedTypeParameters =
val klassExport = "$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}" if (typeParameters.isNotEmpty())
val staticsExport = "<" + typeParameters.joinToString(", ") + ">"
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else "" else
""
if (name.isValidES5Identifier()) klassExport + staticsExport 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 { fun ExportedType.toExtendsClause(indent: String): String {
return when (this) { val isImplicitlyExportedType = this is ExportedType.ImplicitlyExportedType
is ExportedType.ImplicitlyExportedType -> " /*${type.toExtendsClause(indent)} */" val extendsClause = " extends ${toTypeScript(indent, isImplicitlyExportedType)}"
else -> " extends ${toTypeScript(indent)}" return when {
isImplicitlyExportedType -> " /*$extendsClause */"
else -> extendsClause
} }
} }
fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent: String): String { fun List<ExportedType>.toImplementsClause(superInterfacesKeyword: String, indent: String): String {
val (exportedInterfaces, nonExportedInterfaces) = partition { it !is ExportedType.ImplicitlyExportedType } val (exportedInterfaces, nonExportedInterfaces) = partition { it !is ExportedType.ImplicitlyExportedType }
val listOfNonExportedInterfaces = nonExportedInterfaces.joinToString(", ") { val listOfNonExportedInterfaces = nonExportedInterfaces.joinToString(", ") {
(it as ExportedType.ImplicitlyExportedType).type.toTypeScript(indent) (it as ExportedType.ImplicitlyExportedType).type.toTypeScript(indent, true)
} }
return when { return when {
exportedInterfaces.isEmpty() && nonExportedInterfaces.isNotEmpty() -> exportedInterfaces.isEmpty() && nonExportedInterfaces.isNotEmpty() ->
@@ -275,39 +279,40 @@ fun ExportedParameter.toTypeScript(indent: String): String {
return "$name$questionMark: $type" return "$name$questionMark: $type"
} }
fun ExportedType.toTypeScript(indent: String): String = when (this) { fun ExportedType.toTypeScript(indent: String, isInCommentContext: Boolean = false): String = when (this) {
is ExportedType.Primitive -> typescript is ExportedType.Primitive -> typescript
is ExportedType.Array -> "Array<${elementType.toTypeScript(indent)}>" is ExportedType.Array -> "Array<${elementType.toTypeScript(indent, isInCommentContext)}>"
is ExportedType.Function -> "(" + parameterTypes is ExportedType.Function -> "(" + parameterTypes
.withIndex() .withIndex()
.joinToString(", ") { (index, type) -> .joinToString(", ") { (index, type) ->
"p$index: ${type.toTypeScript(indent)}" "p$index: ${type.toTypeScript(indent, isInCommentContext)}"
} + ") => " + returnType.toTypeScript(indent) } + ") => " + returnType.toTypeScript(indent, isInCommentContext)
is ExportedType.ClassType -> is ExportedType.ClassType ->
name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent) }}>" else "" name + if (arguments.isNotEmpty()) "<${arguments.joinToString(", ") { it.toTypeScript(indent, isInCommentContext) }}>" else ""
is ExportedType.TypeOf -> is ExportedType.TypeOf ->
"typeof $name" "typeof $name"
is ExportedType.ErrorType -> "any /*$comment*/" is ExportedType.ErrorType -> if (isInCommentContext) comment else "any /*$comment*/"
is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript(indent) + ">" is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript(indent, isInCommentContext) + ">"
is ExportedType.InlineInterfaceType -> { is ExportedType.InlineInterfaceType -> {
members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" } members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
} }
is ExportedType.IntersectionType -> { is ExportedType.IntersectionType -> {
lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent) lhs.toTypeScript(indent) + " & " + rhs.toTypeScript(indent, isInCommentContext)
} }
is ExportedType.UnionType -> { is ExportedType.UnionType -> {
lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent) lhs.toTypeScript(indent) + " | " + rhs.toTypeScript(indent, isInCommentContext)
} }
is ExportedType.LiteralType.StringLiteralType -> "\"$value\"" is ExportedType.LiteralType.StringLiteralType -> "\"$value\""
is ExportedType.LiteralType.NumberLiteralType -> value.toString() is ExportedType.LiteralType.NumberLiteralType -> value.toString()
is ExportedType.ImplicitlyExportedType -> { is ExportedType.ImplicitlyExportedType -> {
ExportedType.Primitive.Any.toTypeScript(indent) + "/* ${type.toTypeScript("")} */" val typeString = type.toTypeScript("", true)
if (isInCommentContext) typeString else ExportedType.Primitive.Any.toTypeScript(indent) + "/* $typeString */"
} }
is ExportedType.TypeParameter -> if (constraint == null) { is ExportedType.TypeParameter -> if (constraint == null) {
name name
} else { } else {
"$name extends ${constraint.toTypeScript(indent)}" "$name extends ${constraint.toTypeScript(indent, isInCommentContext)}"
} }
} }
@@ -31,5 +31,11 @@ declare namespace JS_TESTS {
class F extends foo.A /* implements foo.NonExportedInterface */ { class F extends foo.A /* implements foo.NonExportedInterface */ {
constructor(); constructor();
} }
class G /* implements foo.NonExportedGenericInterface<foo.NonExportedType> */ {
constructor();
}
class H /* extends foo.NonExportedGenericType<foo.NonExportedType> */ {
constructor();
}
} }
} }
@@ -9,7 +9,9 @@
package foo package foo
interface NonExportedInterface interface NonExportedInterface
interface NonExportedGenericInterface<T>
open class NonExportedType(val value: Int) open class NonExportedType(val value: Int)
open class NonExportedGenericType<T>(val value: T)
@JsExport @JsExport
interface ExportedInterface interface ExportedInterface
@@ -45,3 +47,9 @@ class E : NonExportedType(42), ExportedInterface
@JsExport @JsExport
class F : A(NonExportedType(42)), NonExportedInterface class F : A(NonExportedType(42)), NonExportedInterface
@JsExport
class G : NonExportedGenericInterface<NonExportedType>
@JsExport
class H : NonExportedGenericType<NonExportedType>(NonExportedType(42))