feat(Escaped Identifiers): add ability to use any symbol wrapped in back ticks.

This commit is contained in:
Artem Kobzar
2021-10-29 17:55:59 +00:00
committed by Space
parent ea10b4a781
commit 979e9f94ef
59 changed files with 1260 additions and 159 deletions
@@ -240,6 +240,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
if (extensionFunctionsInExternals) {
this[LanguageFeature.JsEnableExtensionFunctionInExternals] = LanguageFeature.State.ENABLED
}
if (!isIrBackendEnabled()) {
this[LanguageFeature.JsAllowInvalidCharsIdentifiersEscaping] = LanguageFeature.State.DISABLED
}
}
}
}
@@ -98,7 +98,7 @@ class ExportModelGenerator(
private fun exportParameter(parameter: IrValueParameter): ExportedParameter {
// Parameter names do not matter in d.ts files. They can be renamed as we like
var parameterName = sanitizeName(parameter.name.asString())
var parameterName = sanitizeName(parameter.name.asString(), withHash = false)
if (parameterName in allReservedWords)
parameterName = "_$parameterName"
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.prototypeOf
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsElementAccess
import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
@@ -26,19 +27,19 @@ class ExportModelToJsStatements(
return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName)) }
}
fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsNameRef?): List<JsStatement> {
fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsExpression?): List<JsStatement> {
return when (declaration) {
is ExportedNamespace -> {
require(namespace != null) { "Only namespaced namespaces are allowed" }
val statements = mutableListOf<JsStatement>()
val elements = declaration.name.split(".")
var currentNamespace = ""
var currentRef: JsNameRef = namespace
var currentRef: JsExpression = namespace
for (element in elements) {
val newNamespace = "$currentNamespace$$element"
val newNameSpaceRef = namespaceToRefMap.getOrPut(newNamespace) {
val varName = JsName(declareNewNamespace(newNamespace), false)
val namespaceRef = JsNameRef(element, currentRef)
val namespaceRef = jsElementAccess(element, currentRef)
statements += JsVars(
JsVars.JsVar(
varName,
@@ -66,7 +67,7 @@ class ExportModelToJsStatements(
} else {
listOf(
jsAssignment(
JsNameRef(declaration.name, namespace),
jsElementAccess(declaration.name, namespace),
JsNameRef(name)
).makeStmt()
)
@@ -87,7 +88,7 @@ class ExportModelToJsStatements(
is ExportedClass -> {
if (declaration.isInterface) return emptyList()
val newNameSpace = JsNameRef(declaration.name, namespace)
val newNameSpace = jsElementAccess(declaration.name, namespace)
val name = namer.getNameForStaticDeclaration(declaration.ir)
val klassExport =
if (namespace == null) {
@@ -9,6 +9,7 @@ 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.parentAsClass
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.serialization.js.ModuleKind
// TODO: Support module kinds other than plain
@@ -22,7 +23,7 @@ fun wrapTypeScript(name: String, moduleKind: ModuleKind, dts: String): String {
val declarationsDts = types + dts
val namespaceName = sanitizeName(name)
val namespaceName = sanitizeName(name, withHash = false)
return when (moduleKind) {
ModuleKind.PLAIN -> "declare namespace $namespaceName {\n$declarationsDts\n}\n"
@@ -73,8 +74,14 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
""
val renderedReturnType = returnType.toTypeScript(indent)
val containsUnresolvedChar = !name.isValidES5Identifier()
"${prefix}$visibility$keyword$name$renderedTypeParameters($renderedParameters): $renderedReturnType;"
val escapedName = when {
isMember && containsUnresolvedChar -> "\"$name\""
else -> name
}
if (!isMember && containsUnresolvedChar) "" else "${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
}
is ExportedConstructor -> {
@@ -95,7 +102,12 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
else -> if (mutable) "let " else "const "
}
val possibleStatic = if (isMember && isStatic) "static " else ""
"$prefix$visibility$possibleStatic$keyword$name: ${type.toTypeScript(indent)};"
val containsUnresolvedChar = !name.isValidES5Identifier()
val memberName = when {
isMember && containsUnresolvedChar -> "\"$name\""
else -> name
}
if (!isMember && containsUnresolvedChar) "" else "$prefix$visibility$possibleStatic$keyword$memberName: ${type.toTypeScript(indent)};"
}
is ExportedClass -> {
@@ -140,7 +152,8 @@ fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): Strin
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 ""
klassExport + staticsExport
if (name.isValidES5Identifier()) klassExport + staticsExport else ""
}
}
@@ -192,7 +205,7 @@ fun ExportedClass.toReadonlyProperty(): ExportedProperty {
}
fun ExportedParameter.toTypeScript(indent: String): String =
"$name: ${type.toTypeScript(indent)}"
"${sanitizeName(name, withHash = false)}: ${type.toTypeScript(indent)}"
fun ExportedType.toTypeScript(indent: String): String = when (this) {
is ExportedType.Primitive -> typescript
@@ -208,8 +221,8 @@ fun ExportedType.toTypeScript(indent: String): String = when (this) {
is ExportedType.TypeOf ->
"typeof $name"
is ExportedType.ErrorType -> "any /*$comment*/"
is ExportedType.TypeParameter -> name
is ExportedType.ErrorType -> "any /*$comment*/"
is ExportedType.Nullable -> "Nullable<" + baseType.toTypeScript(indent) + ">"
is ExportedType.InlineInterfaceType -> {
members.joinToString(prefix = "{\n", postfix = "$indent}", separator = "") { it.toTypeScript("$indent ") + "\n" }
@@ -102,7 +102,8 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
override fun visitGetValue(expression: IrGetValue, context: JsGenerationContext): JsExpression {
if (expression.symbol.owner.isThisReceiver()) return JsThisRef().withSource(expression, context)
return context.getNameForValueDeclaration(expression.symbol.owner).makeRef().withSource(expression, context)
val possibleGlobalVarRef = context.getNameForValueDeclaration(expression.symbol.owner).makeRef().withSource(expression, context)
return jsGlobalVarRef(possibleGlobalVarRef)
}
override fun visitGetObjectValue(expression: IrGetObjectValue, context: JsGenerationContext): JsExpression {
@@ -110,18 +111,20 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
assert(obj.kind == ClassKind.OBJECT)
assert(obj.isEffectivelyExternal()) { "Non external IrGetObjectValue must be lowered" }
return context.getRefForExternalClass(obj).withSource(expression, context)
val possibleGlobalVarRef = context.getRefForExternalClass(obj).withSource(expression, context)
return jsGlobalVarRef(possibleGlobalVarRef)
}
override fun visitSetField(expression: IrSetField, context: JsGenerationContext): JsExpression {
val fieldName = context.getNameForField(expression.symbol.owner)
val dest = JsNameRef(fieldName, expression.receiver?.accept(this, context))
val dest = jsElementAccess(fieldName.ident, expression.receiver?.accept(this, context))
val source = expression.value.accept(this, context)
return jsAssignment(dest, source).withSource(expression, context)
}
override fun visitSetValue(expression: IrSetValue, context: JsGenerationContext): JsExpression {
val ref = JsNameRef(context.getNameForValueDeclaration(expression.symbol.owner))
val possibleGlobalRef = JsNameRef(context.getNameForValueDeclaration(expression.symbol.owner))
val ref = jsGlobalVarRef(possibleGlobalRef)
val value = expression.value.accept(this, context)
return JsBinaryOperation(JsBinaryOperator.ASG, ref, value).withSource(expression, context)
}
@@ -243,7 +246,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
}
override fun visitDynamicMemberExpression(expression: IrDynamicMemberExpression, data: JsGenerationContext): JsExpression =
JsNameRef(expression.memberName, expression.receiver.accept(this, data)).withSource(expression, data)
jsElementAccess(expression.memberName, expression.receiver.accept(this, data)).withSource(expression, data)
override fun visitDynamicOperatorExpression(expression: IrDynamicOperatorExpression, data: JsGenerationContext): JsExpression =
when (expression.operator) {
@@ -153,6 +153,7 @@ class IrModuleToJsTransformer(
val moduleBody = generateModuleBody(modules, staticContext)
val internalModuleName = JsName("_", false)
val globalThisDeclaration = jsGlobalThisPolyfill();
val globalNames = NameTable<String>(namer.globalNames)
val exportStatements = ExportModelToJsStatements(nameGenerator) { globalNames.declareFreshName(it, it) }
.generateModuleExport(exportedModule, internalModuleName)
@@ -163,6 +164,7 @@ class IrModuleToJsTransformer(
val program = JsProgram()
if (generateScriptModule) {
with(program.globalBlock) {
statements += globalThisDeclaration
statements.addWithComment("block: imports", importStatements + crossModuleImports)
statements += moduleBody
statements.addWithComment("block: exports", exportStatements + crossModuleExports)
@@ -172,6 +174,7 @@ class IrModuleToJsTransformer(
parameters += JsParameter(internalModuleName)
parameters += (importedJsModules + importedKotlinModules).map { JsParameter(it.internalName) }
with(body) {
statements += globalThisDeclaration
statements.addWithComment("block: imports", importStatements + crossModuleImports)
statements += moduleBody
statements.addWithComment("block: exports", exportStatements + crossModuleExports)
@@ -411,7 +414,7 @@ class IrModuleToJsTransformer(
assert(jsModule != null || jsQualifier != null)
val qualifiedReference: JsNameRef
val qualifiedReference: JsExpression
if (jsModule != null) {
val internalName = declareFreshGlobal("\$module\$$jsModule")
@@ -433,7 +436,7 @@ class IrModuleToJsTransformer(
.forEach { declaration ->
val declName = getNameForExternalDeclaration(declaration)
importStatements.add(
JsVars(JsVars.JsVar(declName, JsNameRef(declaration.getJsNameOrKotlinName().identifier, qualifiedReference)))
JsVars(JsVars.JsVar(declName, jsElementAccess(declaration.getJsNameOrKotlinName().identifier, qualifiedReference)))
)
}
}
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.utils.addIfNotNull
class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationContext) {
@@ -77,11 +78,13 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
properties.addIfNotNull(declaration.correspondingPropertySymbol?.owner)
if (es6mode) {
val (_, function) = generateMemberFunction(declaration)
val (memberRef, function) = generateMemberFunction(declaration)
function?.let { jsClass.members += it }
declaration.generateAssignmentIfMangled(memberRef)
} else {
val (memberRef, function) = generateMemberFunction(declaration)
function?.let { classBlock.statements += jsAssignment(memberRef, it.apply { name = null }).makeStmt() }
declaration.generateAssignmentIfMangled(memberRef)
}
}
is IrClass -> {
@@ -204,6 +207,24 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
return classBlock
}
private fun IrSimpleFunction.generateAssignmentIfMangled(memberRef: JsExpression) {
if (
irClass.isExported(context.staticContext.backendContext) &&
visibility.isPublicAPI && hasMangledName() &&
correspondingPropertySymbol == null
) {
classBlock.statements += jsAssignment(prototypeAccessRef(), memberRef).makeStmt()
}
}
private fun IrSimpleFunction.hasMangledName(): Boolean {
return getJsName() == null && !name.asString().isValidES5Identifier()
}
private fun IrSimpleFunction.prototypeAccessRef(): JsExpression {
return jsElementAccess(name.asString(), classPrototypeRef)
}
private fun IrSimpleFunction.overridesExternal(): Boolean {
if (this.isEffectivelyExternal()) return true
@@ -214,9 +235,9 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
return isInterface && !isEffectivelyExternal()
}
private fun generateMemberFunction(declaration: IrSimpleFunction): Pair<JsNameRef, JsFunction?> {
private fun generateMemberFunction(declaration: IrSimpleFunction): Pair<JsExpression, JsFunction?> {
val memberName = context.getNameForMemberFunction(declaration.realOverrideTarget)
val memberRef = JsNameRef(memberName, classPrototypeRef)
val memberRef = jsElementAccess(memberName.ident, classPrototypeRef)
if (declaration.isReal && declaration.body != null) {
val translatedFunction: JsFunction = declaration.accept(IrFunctionToJsTransformer(), context)
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.ir.backend.js.utils.RESERVED_IDENTIFIERS
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.naming.isValidES5Identifier
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.serialization.js.ModuleKind
object ModuleWrapperTranslation {
@@ -123,5 +123,5 @@ val IrModuleFragment.safeName: String
if (result.startsWith('<')) result = result.substring(1)
if (result.endsWith('>')) result = result.substring(0, result.length - 1)
return sanitizeName("kotlin-$result")
return sanitizeName("kotlin_$result")
}
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -43,6 +44,39 @@ fun <T : JsNode> IrWhen.toJsNode(
}
}
// https://mathiasbynens.be/notes/globalthis
// TODO: add DCE for globalThis polyfill declaration
fun jsGlobalThisPolyfill(): List<JsStatement> =
parseJsCode(
"""
(function() {
if (typeof globalThis === 'object') return;
Object.defineProperty(Object.prototype, '__magic__', {
get: function() {
return this;
},
configurable: true
});
__magic__.globalThis = __magic__;
delete Object.prototype.__magic__;
}());
""".trimIndent()
) ?: emptyList()
fun jsElementAccess(name: String, receiver: JsExpression?): JsExpression =
if (receiver == null || name.isValidES5Identifier()) {
JsNameRef(JsName(name, false), receiver)
} else {
JsArrayAccess(receiver, JsStringLiteral(name))
}
fun jsGlobalVarRef(ref: JsNameRef): JsExpression =
if (ref.qualifier != null || ref.ident.isValidES5Identifier()) {
ref
} else {
jsElementAccess(ref.ident, JsNameRef("globalThis"))
}
fun jsAssignment(left: JsExpression, right: JsExpression) = JsBinaryOperation(JsBinaryOperator.ASG, left, right)
fun prototypeOf(classNameRef: JsExpression) = JsNameRef(Namer.PROTOTYPE_NAME, classNameRef)
@@ -114,7 +148,11 @@ fun translateCall(
if (function.getJsName() == null) {
val property = function.correspondingPropertySymbol?.owner
if (property != null && property.isEffectivelyExternal()) {
val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver)
val propertyName = context.getNameForProperty(property)
val nameRef = when (jsDispatchReceiver) {
null -> jsGlobalVarRef(JsNameRef(propertyName))
else -> jsElementAccess(propertyName.ident, jsDispatchReceiver)
}
return when (function) {
property.getter -> nameRef
property.setter -> jsAssignment(nameRef, arguments.single())
@@ -156,8 +194,8 @@ fun translateCall(
}
val ref = when (jsDispatchReceiver) {
null -> JsNameRef(symbolName)
else -> JsNameRef(symbolName, jsDispatchReceiver)
null -> jsGlobalVarRef(JsNameRef(symbolName))
else -> jsElementAccess(symbolName.ident, jsDispatchReceiver)
}
return if (isExternalVararg) {
@@ -174,7 +212,7 @@ fun translateCall(
if (jsDispatchReceiver != null) {
if (argumentsAsSingleArray is JsArrayLiteral) {
JsInvocation(
JsNameRef(symbolName, jsDispatchReceiver),
jsElementAccess(symbolName.ident, jsDispatchReceiver),
argumentsAsSingleArray.expressions
)
} else {
@@ -188,7 +226,7 @@ fun translateCall(
JsVars(JsVars.JsVar(receiverName, jsDispatchReceiver)),
JsReturn(
JsInvocation(
JsNameRef("apply", JsNameRef(symbolName, receiverRef)),
JsNameRef("apply", jsElementAccess(symbolName.ident, receiverRef)),
listOf(
receiverRef,
argumentsAsSingleArray
@@ -20,9 +20,9 @@ import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.js.naming.isES5IdentifierPart
import org.jetbrains.kotlin.js.naming.isES5IdentifierStart
import org.jetbrains.kotlin.js.naming.isValidES5Identifier
import org.jetbrains.kotlin.js.common.isES5IdentifierPart
import org.jetbrains.kotlin.js.common.isES5IdentifierStart
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import java.util.*
@@ -386,23 +386,30 @@ class LocalNameGenerator(val variableNames: NameTable<IrDeclaration>) : IrElemen
}
}
fun sanitizeName(name: String): String {
fun sanitizeName(name: String, withHash: Boolean = true): String {
if (name.isValidES5Identifier()) return name
if (name.isEmpty()) return "_"
val builder = StringBuilder()
val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' }
builder.append(first)
val first = name.first()
builder.append(first.mangleIfNot(Char::isES5IdentifierStart))
for (idx in 1..name.lastIndex) {
val c = name[idx]
builder.append(if (c.isES5IdentifierPart()) c else '_')
builder.append(c.mangleIfNot(Char::isES5IdentifierPart))
}
return builder.toString()
return if (withHash) {
"${builder}_${name.hashCode().toUInt()}"
} else {
builder.toString()
}
}
private inline fun Char.mangleIfNot(predicate: Char.() -> Boolean) =
if (predicate()) this else '_'
private const val SYNTHETIC_LOOP_LABEL = "\$l\$loop"
private const val SYNTHETIC_BLOCK_LABEL = "\$l\$block"
@@ -114,5 +114,5 @@ val RESERVED_IDENTIFIERS = setOf(
"Math", "String", "Boolean", "Date", "Array", "RegExp", "JSON", "Map",
// global identifiers usually declared in know environments (node.js, browser, require.js, WebWorkers, etc)
"require", "define", "module", "window", "self"
"require", "define", "module", "window", "self", "globalThis"
)
+1 -4
View File
@@ -1,8 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_BACKEND: JS
// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping
// Exclamation marks are not valid in names in the dex file format.
// Therefore, do not attempt to dex this file as it will fail.
@@ -1,8 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_BACKEND: JS
// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping
// Names with spaces are not valid according to the dex specification
// before DEX version 040. Therefore, do not attempt to dex the resulting
+2 -4
View File
@@ -1,8 +1,6 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// !SANITIZE_PARENTHESES
// IGNORE_BACKEND: JS, JS_IR
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_BACKEND: JS
// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping
// Sanitization is needed here because DxChecker reports ParseException on parentheses in names.
@@ -0,0 +1,29 @@
// SKIP_TXT
// IGNORE_BACKEND: JS
// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping
private fun ` .private `(): String = TODO("")
fun ` .public `(): String = TODO("")
@JsName(" __ ")
fun foo(): String = TODO("")
@JsName(" ___ ")
private fun bar(): String = TODO("")
@JsName("validName")
private fun ` .private with @JsName `(): String = TODO("")
private class ` .private class ` {
val ` .field. ` = ""
}
val x: Int
@JsName(".")
get() = TODO("")
fun box(x: dynamic) {
x.`foo-bar`()
x.`ba-z`
}
@@ -637,6 +637,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
runTest("compiler/testData/diagnostics/testsWithJsStdLib/name/illegalName.kt");
}
@Test
@TestMetadata("illegalNameIR.kt")
public void testIllegalNameIR() throws Exception {
runTest("compiler/testData/diagnostics/testsWithJsStdLib/name/illegalNameIR.kt");
}
@Test
@TestMetadata("illegalPackageName.kt")
public void testIllegalPackageName() throws Exception {
@@ -262,6 +262,7 @@ enum class LanguageFeature(
ProhibitComparisonOfIncompatibleClasses(sinceVersion = null, kind = BUG_FIX, defaultState = State.DISABLED),
ExplicitBackingFields(sinceVersion = null, defaultState = State.DISABLED, kind = UNSTABLE_FEATURE),
FunctionalTypeWithExtensionAsSupertype(sinceVersion = KOTLIN_1_6, defaultState = State.DISABLED),
JsAllowInvalidCharsIdentifiersEscaping(sinceVersion = null, defaultState = State.DISABLED, kind = UNSTABLE_FEATURE)
;