diff --git a/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/escapeIdentificators.kt b/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/escapeIdentificators.kt index 0db34e121..5381d231c 100644 --- a/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/escapeIdentificators.kt +++ b/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/escapeIdentificators.kt @@ -24,54 +24,54 @@ private val CONTAINS_ONLY_UNDERSCORES = "_+".toRegex() private val STARTS_WITH_NUMBER = "^\\d+".toRegex() private val RESERVED_WORDS = setOf( - "as", - "break", - "class", - "continue", - "do", - "else", - "false", - "for", - "fun", - "if", - "in", - "interface", - "is", - "null", - "object", - "package", - "return", - "super", - "this", - "throw", - "true", - "try", - "typealias", - "typeof", - "val", - "var", - "when", - "while" + "as", + "break", + "class", + "continue", + "do", + "else", + "false", + "for", + "fun", + "if", + "in", + "interface", + "is", + "null", + "object", + "package", + "return", + "super", + "this", + "throw", + "true", + "try", + "typealias", + "typeof", + "val", + "var", + "when", + "while" ) private val RENAME_PARAM_MAP = mapOf( - Pair("this", "self"), - - Pair("as", "param_as"), - Pair("class", "param_class"), - Pair("fun", "param_fun"), - Pair("in", "param_in"), - Pair("interface", "param_interface"), - Pair("in", "param_in"), - Pair("is", "param_is"), - Pair("object", "obj"), - Pair("package", "param_package"), - Pair("return", "param_return"), - Pair("throw", "param_throw"), - Pair("try", "param_try"), - Pair("typealias", "param_typealias"), - Pair("val", "param_val"), - Pair("when", "param_when") + Pair("this", "self"), + + Pair("as", "param_as"), + Pair("class", "param_class"), + Pair("fun", "param_fun"), + Pair("in", "param_in"), + Pair("interface", "param_interface"), + Pair("in", "param_in"), + Pair("is", "param_is"), + Pair("object", "obj"), + Pair("package", "param_package"), + Pair("return", "param_return"), + Pair("throw", "param_throw"), + Pair("try", "param_try"), + Pair("typealias", "param_typealias"), + Pair("val", "param_val"), + Pair("when", "param_when") ) private fun String.renameAsParameter(): String { @@ -85,18 +85,26 @@ private fun NameEntity.renameAsParameter(): NameEntity { } } +private fun Char.isLetterOrDigitOrUnderscore(): Boolean { + return isLetterOrDigit() || equals('_') +} + +private fun String.isValidKotlinIdentifier(): Boolean { + return isNotEmpty() && + all(Char::isLetterOrDigitOrUnderscore) && + (!startsWith('_') || any { it != '_' }) +} + +private fun String.isEscaped(): Boolean { + return startsWith('`') && endsWith('`') +} + +private fun String.isReservedKeyword(): Boolean { + return RESERVED_WORDS.contains(this) +} + private fun String.shouldEscape(): Boolean { - val isReservedWord = RESERVED_WORDS.contains(this) - val containsDollarSign = this.contains("$") - val containsMinusSign = this.contains("-") - val containsOnlyUnderscores = CONTAINS_ONLY_UNDERSCORES.matches(this) - val startsWithNumber = this.contains(STARTS_WITH_NUMBER) - val isEscapedAlready = this.startsWith("`") - val isStartingWithColon = this.startsWith(":") - val isStartingWithDot = this.startsWith(".") - - return !isEscapedAlready && - (isReservedWord || containsDollarSign || containsOnlyUnderscores || containsMinusSign || startsWithNumber || isStartingWithColon || isStartingWithDot) + return !isEscaped() && (isReservedKeyword() || !isValidKotlinIdentifier()) } private fun String.escape(): String { @@ -139,12 +147,12 @@ private class EscapeIdentificatorsTypeLowering : ModelWithOwnerTypeLowering { override fun lowerTypeValueModel(ownerContext: NodeOwner): TypeValueModel { return super.lowerTypeValueModel( - ownerContext.copy( - node = ownerContext.node.copy( - value = ownerContext.node.value.escape(), - fqName = ownerContext.node.fqName?.escape() - ) + ownerContext.copy( + node = ownerContext.node.copy( + value = ownerContext.node.value.escape(), + fqName = ownerContext.node.fqName?.escape() ) + ) ) } @@ -160,14 +168,21 @@ private class EscapeIdentificatorsTypeLowering : ModelWithOwnerTypeLowering { override fun lowerFunctionModel(ownerContext: NodeOwner, parentModule: ModuleModel): FunctionModel { val declaration = ownerContext.node - return super.lowerFunctionModel(ownerContext.copy(node = declaration.copy( - name = declaration.name.escape() - )), parentModule) + return super.lowerFunctionModel( + ownerContext.copy( + node = declaration.copy( + name = declaration.name.escape() + ) + ), parentModule + ) } override fun lowerVariableModel(ownerContext: NodeOwner, parentModule: ModuleModel?): VariableModel { val declaration = ownerContext.node - return super.lowerVariableModel(ownerContext.copy(node = declaration.copy(name = declaration.name.escape())), parentModule) + return super.lowerVariableModel( + ownerContext.copy(node = declaration.copy(name = declaration.name.escape())), + parentModule + ) } @@ -184,27 +199,45 @@ private class EscapeIdentificatorsTypeLowering : ModelWithOwnerTypeLowering { } - override fun lowerInterfaceModel(ownerContext: NodeOwner, parentModule: ModuleModel): InterfaceModel { + override fun lowerInterfaceModel( + ownerContext: NodeOwner, + parentModule: ModuleModel + ): InterfaceModel { val declaration = ownerContext.node - return super.lowerInterfaceModel(ownerContext.copy(node = declaration.copy( - name = declaration.name.escape() - )), parentModule) + return super.lowerInterfaceModel( + ownerContext.copy( + node = declaration.copy( + name = declaration.name.escape() + ) + ), parentModule + ) } override fun lowerClassModel(ownerContext: NodeOwner, parentModule: ModuleModel): ClassModel { val declaration = ownerContext.node - return super.lowerClassModel(ownerContext.copy(node = declaration.copy( - name = declaration.name.escape() - )), parentModule) + return super.lowerClassModel( + ownerContext.copy( + node = declaration.copy( + name = declaration.name.escape() + ) + ), parentModule + ) } - override fun lowerTypeAliasModel(ownerContext: NodeOwner, parentModule: ModuleModel): TypeAliasModel { + override fun lowerTypeAliasModel( + ownerContext: NodeOwner, + parentModule: ModuleModel + ): TypeAliasModel { val declaration = ownerContext.node - return super.lowerTypeAliasModel(ownerContext.copy(node = declaration.copy( - name = declaration.name.escape() - )), parentModule) + return super.lowerTypeAliasModel( + ownerContext.copy( + node = declaration.copy( + name = declaration.name.escape() + ) + ), parentModule + ) } override fun lowerEnumModel(ownerContext: NodeOwner, parentModule: ModuleModel): EnumModel { @@ -214,10 +247,10 @@ private class EscapeIdentificatorsTypeLowering : ModelWithOwnerTypeLowering { override fun lowerRoot(moduleModel: ModuleModel, ownerContext: NodeOwner): ModuleModel { return moduleModel.copy( - name = moduleModel.name.escape(), - shortName = moduleModel.shortName.escape(), - declarations = lowerTopLevelDeclarations(moduleModel.declarations, ownerContext, moduleModel), - submodules = moduleModel.submodules.map { submodule -> lowerRoot(submodule, ownerContext.wrap(submodule)) } + name = moduleModel.name.escape(), + shortName = moduleModel.shortName.escape(), + declarations = lowerTopLevelDeclarations(moduleModel.declarations, ownerContext, moduleModel), + submodules = moduleModel.submodules.map { submodule -> lowerRoot(submodule, ownerContext.wrap(submodule)) } ) } } diff --git a/model-lowerings/src/org/jetbrains/dukat/commonLowerings/removeUnsupportedJsNames.kt b/model-lowerings/src/org/jetbrains/dukat/commonLowerings/removeUnsupportedJsNames.kt deleted file mode 100644 index 1c2b9270f..000000000 --- a/model-lowerings/src/org/jetbrains/dukat/commonLowerings/removeUnsupportedJsNames.kt +++ /dev/null @@ -1,189 +0,0 @@ -package org.jetbrains.dukat.commonLowerings - -import org.jetbrains.dukat.astCommon.IdentifierEntity -import org.jetbrains.dukat.astCommon.NameEntity -import org.jetbrains.dukat.astCommon.QualifierEntity -import org.jetbrains.dukat.astModel.AnnotationModel -import org.jetbrains.dukat.astModel.FunctionTypeModel -import org.jetbrains.dukat.astModel.InterfaceModel -import org.jetbrains.dukat.astModel.MemberModel -import org.jetbrains.dukat.astModel.MethodModel -import org.jetbrains.dukat.astModel.ModuleModel -import org.jetbrains.dukat.astModel.ParameterModel -import org.jetbrains.dukat.astModel.PropertyModel -import org.jetbrains.dukat.astModel.TypeModel -import org.jetbrains.dukat.astModel.TypeParameterModel -import org.jetbrains.dukat.astModel.TypeParameterReferenceModel -import org.jetbrains.dukat.astModel.TypeValueModel -import org.jetbrains.dukat.model.commonLowerings.ModelLowering -import org.jetbrains.dukat.model.commonLowerings.ModelWithOwnerTypeLowering -import org.jetbrains.dukat.ownerContext.NodeOwner - -private fun NameEntity.startsWith(prefix: String): Boolean { - return when (this) { - is IdentifierEntity -> value.startsWith(prefix) - is QualifierEntity -> right.startsWith(prefix) - } -} - -private fun NameEntity.contains(pattern: Regex): Boolean { - return when (this) { - is IdentifierEntity -> value.contains(pattern) - is QualifierEntity -> left.contains(pattern) - } -} - -private fun NameEntity.contains(str: String): Boolean { - return when (this) { - is IdentifierEntity -> value.contains(str) - is QualifierEntity -> left.contains(str) - } -} - -private fun Char.isLetterOrDigitOrUnderscore(): Boolean { - return isLetterOrDigit() || equals('_') -} - -private fun String.isEscaped(): Boolean { - return startsWith('`') && endsWith('`') -} - -private fun String.unescaped(): String { - return when { - isEscaped() -> substring(1 until lastIndex) - else -> this - } -} - -private fun IdentifierEntity.isValidForJs(): Boolean { - return with(value.unescaped()) { - isNotEmpty() && all(Char::isLetterOrDigitOrUnderscore) && !first().isDigit() - } -} - -private val STARTS_WITH_NUMBER = "^`\\d+".toRegex() - -private fun NameEntity.isInvalidJsName(): Boolean { - return when (this) { - is IdentifierEntity -> !isValidForJs() - is QualifierEntity -> contains(STARTS_WITH_NUMBER) || - contains("-") || - startsWith("`:") || - startsWith("`.") - } -} - -private fun MemberModel.getType(): TypeModel? { - return when (this) { - is MethodModel -> type - is PropertyModel -> type - else -> null - } -} - -private fun resolveCommonType(members: List): TypeModel { - val firstMember = members.firstOrNull() - val firstMemberType = firstMember?.getType() - - return firstMemberType?.let { firstType -> - if (members.all { member -> member.getType() == firstMemberType }) { - firstType - } else null - } ?: TypeValueModel(IdentifierEntity("Any"), emptyList(), null, null, true) -} - -private fun TypeModel.makeNullable(): TypeModel { - return when(this) { - is TypeValueModel -> copy(nullable = true) - is FunctionTypeModel -> copy(nullable = true) - is TypeParameterReferenceModel -> copy(nullable = true) - is TypeParameterModel -> copy(nullable = true) - else -> this - } -} - -private class UnsupportedJsNamesLowering : ModelWithOwnerTypeLowering { - - override fun lowerInterfaceModel(ownerContext: NodeOwner, parentModule: ModuleModel): InterfaceModel { - val declaration = ownerContext.node - val (unsupportedMembers, supportedMembers) = declaration.members.partition { member -> - val name = when (member) { - is MethodModel -> member.name - is PropertyModel -> member.name - else -> null - } - - name?.isInvalidJsName() == true - } - - val commonType = resolveCommonType(unsupportedMembers) - - val resolvedMembers = if (unsupportedMembers.isNotEmpty()) { - - val unsupportedGetter = MethodModel( - name = IdentifierEntity("get"), - parameters = listOf(ParameterModel( - name = "key", - type = TypeValueModel(IdentifierEntity("String"), emptyList(), null, null), - initializer = null, - vararg = false, - modifier = null - )), - type = commonType.makeNullable(), - typeParameters = emptyList(), - static = false, - override = null, - operator = true, - annotations = listOf(AnnotationModel.NATIVE_GETTER), - open = false, - body = null - ) - - val unrolledTypes = unsupportedMembers.mapNotNull { it.getType() } - - val unsupportedSetters = unrolledTypes.map {setterType -> - MethodModel( - name = IdentifierEntity("set"), - parameters = listOf(ParameterModel( - name = "key", - type = TypeValueModel(IdentifierEntity("String"), emptyList(), null, null), - initializer = null, - vararg = false, - modifier = null - ), ParameterModel( - name = "value", - type = setterType, - initializer = null, - vararg = false, - modifier = null - )), - type = TypeValueModel(IdentifierEntity("Unit"), emptyList(), null, null), - typeParameters = emptyList(), - static = false, - override = null, - operator = true, - annotations = listOf(AnnotationModel.NATIVE_SETTER), - open = false, - body = null - ) - } - - listOf(unsupportedGetter) + unsupportedSetters + supportedMembers - } else { - supportedMembers - } - - val declarationLowered = declaration.copy( - members = resolvedMembers - ) - - return super.lowerInterfaceModel(ownerContext.copy(node = declarationLowered), parentModule) - } - -} - -class RemoveUnsupportedJsNames : ModelLowering { - override fun lower(module: ModuleModel): ModuleModel { - return UnsupportedJsNamesLowering().lowerRoot(module, NodeOwner(module, null)) - } -} \ No newline at end of file diff --git a/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt b/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt index a73d24a70..df80f271a 100644 --- a/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt +++ b/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt @@ -9,7 +9,6 @@ import org.jetbrains.dukat.commonLowerings.AnyfyUnresolvedTypes import org.jetbrains.dukat.commonLowerings.ExtractNestedInheritedInterfaces import org.jetbrains.dukat.commonLowerings.RemoveDuplicateMembers import org.jetbrains.dukat.commonLowerings.RemoveParentAny -import org.jetbrains.dukat.commonLowerings.RemoveUnsupportedJsNames import org.jetbrains.dukat.commonLowerings.RemoveUnusedGeneratedInterfaces import org.jetbrains.dukat.commonLowerings.ReplaceSimpleGeneratedInterfacesWithLambdas import org.jetbrains.dukat.commonLowerings.SeparateNonExternalEntities @@ -105,7 +104,6 @@ open class TypescriptLowerer( SubstituteTsStdLibEntities(), RemoveParentAny(), EscapeIdentificators(), - RemoveUnsupportedJsNames(), MergeClassLikesAndModuleDeclarations(), MergeVarsAndInterfaces(), ExtractNestedInheritedInterfaces(),