feat(Escaped Identifiers): add ability to use any symbol wrapped in back ticks.
This commit is contained in:
+3
@@ -240,6 +240,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
||||
if (extensionFunctionsInExternals) {
|
||||
this[LanguageFeature.JsEnableExtensionFunctionInExternals] = LanguageFeature.State.ENABLED
|
||||
}
|
||||
if (!isIrBackendEnabled()) {
|
||||
this[LanguageFeature.JsAllowInvalidCharsIdentifiersEscaping] = LanguageFeature.State.DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
|
||||
+6
-5
@@ -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) {
|
||||
|
||||
+19
-6
@@ -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" }
|
||||
|
||||
+8
-5
@@ -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) {
|
||||
|
||||
+5
-2
@@ -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)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-3
@@ -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)
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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")
|
||||
}
|
||||
|
||||
+43
-5
@@ -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"
|
||||
|
||||
+1
-1
@@ -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,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
@@ -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`
|
||||
}
|
||||
+6
@@ -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)
|
||||
|
||||
;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user