diff --git a/.idea/misc.xml b/.idea/misc.xml index d1ca2e70450..6e5ccca3e24 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -70,7 +70,7 @@ http://www.w3.org/1999/xhtml - + diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index ce3ed60bb95..05465d3fd8c 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -240,6 +240,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() { if (extensionFunctionsInExternals) { this[LanguageFeature.JsEnableExtensionFunctionInExternals] = LanguageFeature.State.ENABLED } + if (!isIrBackendEnabled()) { + this[LanguageFeature.JsAllowInvalidCharsIdentifiersEscaping] = LanguageFeature.State.DISABLED + } } } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index b250451f10f..e27f9d98cdf 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -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" diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt index e4aea9ef9da..c742cefde71 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -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 { + fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsExpression?): List { return when (declaration) { is ExportedNamespace -> { require(namespace != null) { "Only namespaced namespaces are allowed" } val statements = mutableListOf() 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) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index 8a8153bcd7d..18a21cf0cc8 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -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" } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 837945b5b10..6645e64a797 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -102,7 +102,8 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer(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))) ) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index 63027c054e4..75b5a264a33 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -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 { + private fun generateMemberFunction(declaration: IrSimpleFunction): Pair { 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) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt index a135b324d36..25b43ebf311 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt @@ -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 { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt index 9d239909e40..7a2702fc8fa 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt @@ -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") } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 7a1dca7d5d7..a172e6c7886 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -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 IrWhen.toJsNode( } } +// https://mathiasbynens.be/notes/globalthis +// TODO: add DCE for globalThis polyfill declaration +fun jsGlobalThisPolyfill(): List = + 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 diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt index 9c5690c7916..c2af3a05fa5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/NameTables.kt @@ -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) : 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" diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/StableNamesCollector.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/StableNamesCollector.kt index 1a721cabff3..4d8b0d73534 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/StableNamesCollector.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/StableNamesCollector.kt @@ -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" ) diff --git a/compiler/testData/codegen/box/classes/quotedClassName.kt b/compiler/testData/codegen/box/classes/quotedClassName.kt index 222edec08c6..f42928afbb2 100644 --- a/compiler/testData/codegen/box/classes/quotedClassName.kt +++ b/compiler/testData/codegen/box/classes/quotedClassName.kt @@ -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. diff --git a/compiler/testData/codegen/box/localClasses/nameWithWhitespace.kt b/compiler/testData/codegen/box/localClasses/nameWithWhitespace.kt index 81961c89dce..3607849f8da 100644 --- a/compiler/testData/codegen/box/localClasses/nameWithWhitespace.kt +++ b/compiler/testData/codegen/box/localClasses/nameWithWhitespace.kt @@ -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 diff --git a/compiler/testData/codegen/box/mangling/parentheses.kt b/compiler/testData/codegen/box/mangling/parentheses.kt index 6fed9586acd..6462a803dab 100644 --- a/compiler/testData/codegen/box/mangling/parentheses.kt +++ b/compiler/testData/codegen/box/mangling/parentheses.kt @@ -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. diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/illegalNameIR.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/illegalNameIR.kt new file mode 100644 index 00000000000..22eca4762a8 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/illegalNameIR.kt @@ -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` +} \ No newline at end of file diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java index 823ea6751de..0870bc56b31 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJsStdLibGenerated.java @@ -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 { diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index e4019e6c961..ace963cbdbd 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -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) ; diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java index ac41ade5fe1..86fe735557b 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.backend.ast.JsDoubleLiteral; import org.jetbrains.kotlin.js.backend.ast.JsIntLiteral; import org.jetbrains.kotlin.js.backend.ast.JsVars.JsVar; +import org.jetbrains.kotlin.js.common.IdentifierPolicyKt; import org.jetbrains.kotlin.js.util.TextOutput; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; @@ -895,7 +896,19 @@ public class JsToStringGenerationVisitor extends JsVisitor { p.print(((JsNameRef) labelExpr).getIdent()); } else if (labelExpr instanceof JsStringLiteral) { - p.print(((JsStringLiteral) labelExpr).getValue()); + JsStringLiteral stringLiteral = (JsStringLiteral) labelExpr; + String value = stringLiteral.getValue(); + boolean isValidIdentifier = IdentifierPolicyKt.isValidES5Identifier(value); + + if (!isValidIdentifier) { + p.print('\''); + } + + p.print(value); + + if (!isValidIdentifier) { + p.print('\''); + } } else { accept(labelExpr); diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/common/IdentifierPolicy.kt b/js/js.ast/src/org/jetbrains/kotlin/js/common/IdentifierPolicy.kt new file mode 100644 index 00000000000..d4f78f968de --- /dev/null +++ b/js/js.ast/src/org/jetbrains/kotlin/js/common/IdentifierPolicy.kt @@ -0,0 +1,52 @@ +package org.jetbrains.kotlin.js.common + +private fun Char.isAllowedLatinLetterOrSpecial(): Boolean { + return this in 'a'..'z' || this in 'A'..'Z' || this == '_' || this == '$' +} + +private fun Char.isAllowedSimpleDigit() = + this in '0'..'9' + +private fun Char.isNotAllowedSimpleCharacter() = when (this) { + ' ', '<', '>', '-', '?' -> true + else -> false +} + +fun Char.isES5IdentifierStart(): Boolean { + if (isAllowedLatinLetterOrSpecial()) return true + if (isNotAllowedSimpleCharacter()) return false + + return isES5IdentifierStartFull() +} + +// See ES 5.1 spec: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6 +private fun Char.isES5IdentifierStartFull() = + Character.isLetter(this) || // Lu | Ll | Lt | Lm | Lo + // Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec + Character.getType(this).toByte() == Character.LETTER_NUMBER + + +fun Char.isES5IdentifierPart(): Boolean { + if (isAllowedLatinLetterOrSpecial()) return true + if (isAllowedSimpleDigit()) return true + if (isNotAllowedSimpleCharacter()) return false + + return isES5IdentifierStartFull() || + when (Character.getType(this).toByte()) { + Character.NON_SPACING_MARK, + Character.COMBINING_SPACING_MARK, + Character.DECIMAL_DIGIT_NUMBER, + Character.CONNECTOR_PUNCTUATION -> true + else -> false + } || + this == '\u200C' || // Zero-width non-joiner + this == '\u200D' // Zero-width joiner +} + +fun String.isValidES5Identifier(): Boolean { + if (isEmpty() || !this[0].isES5IdentifierStart()) return false + for (idx in 1 until length) { + if (!get(idx).isES5IdentifierPart()) return false + } + return true +} \ No newline at end of file diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt index cb9abc35d09..0b9e97cae12 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/naming/NameSuggestion.kt @@ -19,6 +19,8 @@ package org.jetbrains.kotlin.js.naming import org.jetbrains.kotlin.builtins.ReflectionTypes import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor +import org.jetbrains.kotlin.js.common.isES5IdentifierPart +import org.jetbrains.kotlin.js.common.isES5IdentifierStart import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.getNameForAnnotatedObject import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils.isNativeObject import org.jetbrains.kotlin.name.Name @@ -358,55 +360,4 @@ class NameSuggestion { return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("") } } -} - -private fun Char.isAllowedLatinLetterOrSpecial(): Boolean { - return this in 'a'..'z' || this in 'A'..'Z' || this == '_' || this == '$' -} - -private fun Char.isAllowedSimpleDigit() = - this in '0'..'9' - -private fun Char.isNotAllowedSimpleCharacter() = when (this) { - ' ', '<', '>', '-', '?' -> true - else -> false -} - -fun Char.isES5IdentifierStart(): Boolean { - if (isAllowedLatinLetterOrSpecial()) return true - if (isNotAllowedSimpleCharacter()) return false - - return isES5IdentifierStartFull() -} - -// See ES 5.1 spec: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6 -private fun Char.isES5IdentifierStartFull() = - Character.isLetter(this) || // Lu | Ll | Lt | Lm | Lo - // Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec - Character.getType(this).toByte() == Character.LETTER_NUMBER - - -fun Char.isES5IdentifierPart(): Boolean { - if (isAllowedLatinLetterOrSpecial()) return true - if (isAllowedSimpleDigit()) return true - if (isNotAllowedSimpleCharacter()) return false - - return isES5IdentifierStartFull() || - when (Character.getType(this).toByte()) { - Character.NON_SPACING_MARK, - Character.COMBINING_SPACING_MARK, - Character.DECIMAL_DIGIT_NUMBER, - Character.CONNECTOR_PUNCTUATION -> true - else -> false - } || - this == '\u200C' || // Zero-width non-joiner - this == '\u200D' // Zero-width joiner -} - -fun String.isValidES5Identifier(): Boolean { - if (isEmpty() || !this[0].isES5IdentifierStart()) return false - for (idx in 1 ..length-1) { - if (!get(idx).isES5IdentifierPart()) return false - } - return true } \ No newline at end of file diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index a6eef1a0486..1457b9f85e1 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -17,28 +17,28 @@ import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker import org.jetbrains.kotlin.types.DynamicTypesAllowed object JsPlatformConfigurator : PlatformConfiguratorBase( - DynamicTypesAllowed(), - additionalDeclarationCheckers = listOf( - NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), - JsNameChecker, JsModuleChecker, JsExternalFileChecker, - JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, - JsRuntimeAnnotationChecker, - JsDynamicDeclarationChecker, - JsExportAnnotationChecker, - JsExportDeclarationChecker - ), - additionalCallCheckers = listOf( - JsModuleCallChecker, - JsDynamicCallChecker, - JsDefinedExternallyCallChecker, - ), - identifierChecker = JsIdentifierChecker + DynamicTypesAllowed(), + additionalDeclarationCheckers = listOf( + NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(), + JsNameChecker, JsModuleChecker, JsExternalFileChecker, + JsExternalChecker, JsInheritanceChecker, JsMultipleInheritanceChecker, + JsRuntimeAnnotationChecker, + JsDynamicDeclarationChecker, + JsExportAnnotationChecker, + JsExportDeclarationChecker + ), + additionalCallCheckers = listOf( + JsModuleCallChecker, + JsDynamicCallChecker, + JsDefinedExternallyCallChecker, + ), ) { override fun configureModuleComponents(container: StorageComponentContainer) { container.useInstance(NameSuggestion()) container.useImpl() container.useImpl() container.useImpl() + container.useImpl() container.useImpl() container.useImpl() container.useInstance(JsModuleClassLiteralChecker) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt index 23789634e0a..77a40b3141f 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsDynamicCallChecker.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.js.naming.NameSuggestion import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -82,7 +83,9 @@ object JsDynamicCallChecker : CallChecker { } private fun checkIdentifier(name: String?, reportOn: PsiElement, context: CallCheckerContext) { - if (name == null) return + if (name == null || context.languageVersionSettings.supportsFeature(LanguageFeature.JsAllowInvalidCharsIdentifiersEscaping)) { + return + } if (NameSuggestion.sanitizeName(name) != name) { context.trace.report(ErrorsJs.NAME_CONTAINS_ILLEGAL_CHARS.on(reportOn)) } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsIdentifierChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsIdentifierChecker.kt index ab57618a4af..147300c6abb 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsIdentifierChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsIdentifierChecker.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.js.resolve.diagnostics +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.js.naming.NameSuggestion @@ -23,8 +25,11 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.resolve.IdentifierChecker -object JsIdentifierChecker : IdentifierChecker { +class JsIdentifierChecker(private val languageVersionSettings: LanguageVersionSettings) : IdentifierChecker { override fun checkIdentifier(simpleNameExpression: KtSimpleNameExpression, diagnosticHolder: DiagnosticSink) { + if (languageVersionSettings.supportsFeature(LanguageFeature.JsAllowInvalidCharsIdentifiersEscaping)) { + return + } val simpleName = simpleNameExpression.getReferencedName() val hasIllegalChars = simpleName.split('.').any { NameSuggestion.sanitizeName(it) != it } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt index 083e301215a..39bd3274737 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor @@ -16,6 +17,9 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext class JsNameCharsChecker(private val suggestion: NameSuggestion) : DeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (context.languageVersionSettings.supportsFeature(LanguageFeature.JsAllowInvalidCharsIdentifiersEscaping)) { + return + } val bindingContext = context.trace.bindingContext if (descriptor is PropertyAccessorDescriptor && AnnotationsUtils.getJsName(descriptor) == null) return diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index 734d162c87c..5eb5a5a23ac 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -2075,6 +2075,99 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { } } + @TestMetadata("js/js.translator/testData/box/escapedIdentifiers") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class EscapedIdentifiers extends AbstractIrBoxJsES6Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath); + } + + public void testAllFilesPresentInEscapedIdentifiers() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/escapedIdentifiers"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); + } + + @TestMetadata("classLikeMemberClassMangling.kt") + public void testClassLikeMemberClassMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberClassMangling.kt"); + } + + @TestMetadata("classLikeMemberFieldMangling.kt") + public void testClassLikeMemberFieldMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFieldMangling.kt"); + } + + @TestMetadata("classLikeMemberFunctionMangling.kt") + public void testClassLikeMemberFunctionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFunctionMangling.kt"); + } + + @TestMetadata("dynamicEscapedField.kt") + public void testDynamicEscapedField() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/dynamicEscapedField.kt"); + } + + @TestMetadata("externalEscapedAMDTopLevel.kt") + public void testExternalEscapedAMDTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.kt"); + } + + @TestMetadata("externalEscapedClassFields.kt") + public void testExternalEscapedClassFields() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.kt"); + } + + @TestMetadata("externalEscapedCommonJSTopLevel.kt") + public void testExternalEscapedCommonJSTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.kt"); + } + + @TestMetadata("externalEscapedTopLevel.kt") + public void testExternalEscapedTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.kt"); + } + + @TestMetadata("topLevelExportedClass.kt") + public void testTopLevelExportedClass() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedClass.kt"); + } + + @TestMetadata("topLevelExportedCompanion.kt") + public void testTopLevelExportedCompanion() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedCompanion.kt"); + } + + @TestMetadata("topLevelExportedFunction.kt") + public void testTopLevelExportedFunction() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt"); + } + + @TestMetadata("topLevelExportedVariable.kt") + public void testTopLevelExportedVariable() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedVariable.kt"); + } + + @TestMetadata("topLevelLocalClassMangling.kt") + public void testTopLevelLocalClassMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalClassMangling.kt"); + } + + @TestMetadata("topLevelLocalCompanionMangling.kt") + public void testTopLevelLocalCompanionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalCompanionMangling.kt"); + } + + @TestMetadata("topLevelLocalFunctionMangling.kt") + public void testTopLevelLocalFunctionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalFunctionMangling.kt"); + } + + @TestMetadata("topLevelLocalVariableMangling.kt") + public void testTopLevelLocalVariableMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalVariableMangling.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/examples") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsTypeScriptExportES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsTypeScriptExportES6TestGenerated.java index 57379b2599d..022d17388a2 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsTypeScriptExportES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsTypeScriptExportES6TestGenerated.java @@ -84,6 +84,24 @@ public class IrJsTypeScriptExportES6TestGenerated extends AbstractIrJsTypeScript } } + @TestMetadata("js/js.translator/testData/typescript-export/escapedDeclarations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class EscapedDeclarations extends AbstractIrJsTypeScriptExportES6Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath); + } + + public void testAllFilesPresentInEscapedDeclarations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/escapedDeclarations"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); + } + + @TestMetadata("escapedDeclarations.kt") + public void testEscapedDeclarations() throws Exception { + runTest("js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.kt"); + } + } + @TestMetadata("js/js.translator/testData/typescript-export/inheritance") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index 330f3428b61..8f3744be978 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -2070,6 +2070,99 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/escapedIdentifiers") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class EscapedIdentifiers extends AbstractIrBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInEscapedIdentifiers() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/escapedIdentifiers"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @TestMetadata("classLikeMemberClassMangling.kt") + public void testClassLikeMemberClassMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberClassMangling.kt"); + } + + @TestMetadata("classLikeMemberFieldMangling.kt") + public void testClassLikeMemberFieldMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFieldMangling.kt"); + } + + @TestMetadata("classLikeMemberFunctionMangling.kt") + public void testClassLikeMemberFunctionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFunctionMangling.kt"); + } + + @TestMetadata("dynamicEscapedField.kt") + public void testDynamicEscapedField() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/dynamicEscapedField.kt"); + } + + @TestMetadata("externalEscapedAMDTopLevel.kt") + public void testExternalEscapedAMDTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.kt"); + } + + @TestMetadata("externalEscapedClassFields.kt") + public void testExternalEscapedClassFields() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.kt"); + } + + @TestMetadata("externalEscapedCommonJSTopLevel.kt") + public void testExternalEscapedCommonJSTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.kt"); + } + + @TestMetadata("externalEscapedTopLevel.kt") + public void testExternalEscapedTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.kt"); + } + + @TestMetadata("topLevelExportedClass.kt") + public void testTopLevelExportedClass() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedClass.kt"); + } + + @TestMetadata("topLevelExportedCompanion.kt") + public void testTopLevelExportedCompanion() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedCompanion.kt"); + } + + @TestMetadata("topLevelExportedFunction.kt") + public void testTopLevelExportedFunction() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt"); + } + + @TestMetadata("topLevelExportedVariable.kt") + public void testTopLevelExportedVariable() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedVariable.kt"); + } + + @TestMetadata("topLevelLocalClassMangling.kt") + public void testTopLevelLocalClassMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalClassMangling.kt"); + } + + @TestMetadata("topLevelLocalCompanionMangling.kt") + public void testTopLevelLocalCompanionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalCompanionMangling.kt"); + } + + @TestMetadata("topLevelLocalFunctionMangling.kt") + public void testTopLevelLocalFunctionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalFunctionMangling.kt"); + } + + @TestMetadata("topLevelLocalVariableMangling.kt") + public void testTopLevelLocalVariableMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalVariableMangling.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/examples") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java index 31d8629c971..207acde5625 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsTypeScriptExportTestGenerated.java @@ -84,6 +84,24 @@ public class IrJsTypeScriptExportTestGenerated extends AbstractIrJsTypeScriptExp } } + @TestMetadata("js/js.translator/testData/typescript-export/escapedDeclarations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class EscapedDeclarations extends AbstractIrJsTypeScriptExportTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInEscapedDeclarations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/typescript-export/escapedDeclarations"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @TestMetadata("escapedDeclarations.kt") + public void testEscapedDeclarations() throws Exception { + runTest("js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.kt"); + } + } + @TestMetadata("js/js.translator/testData/typescript-export/inheritance") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testNew/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testNew/BoxJsTestGenerated.java index 809a598b062..1a56ebd8e5c 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testNew/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/testNew/BoxJsTestGenerated.java @@ -1915,6 +1915,112 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/escapedIdentifiers") + @TestDataPath("$PROJECT_ROOT") + public class EscapedIdentifiers { + @Test + public void testAllFilesPresentInEscapedIdentifiers() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/escapedIdentifiers"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @Test + @TestMetadata("classLikeMemberClassMangling.kt") + public void testClassLikeMemberClassMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberClassMangling.kt"); + } + + @Test + @TestMetadata("classLikeMemberFieldMangling.kt") + public void testClassLikeMemberFieldMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFieldMangling.kt"); + } + + @Test + @TestMetadata("classLikeMemberFunctionMangling.kt") + public void testClassLikeMemberFunctionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFunctionMangling.kt"); + } + + @Test + @TestMetadata("dynamicEscapedField.kt") + public void testDynamicEscapedField() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/dynamicEscapedField.kt"); + } + + @Test + @TestMetadata("externalEscapedAMDTopLevel.kt") + public void testExternalEscapedAMDTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.kt"); + } + + @Test + @TestMetadata("externalEscapedClassFields.kt") + public void testExternalEscapedClassFields() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.kt"); + } + + @Test + @TestMetadata("externalEscapedCommonJSTopLevel.kt") + public void testExternalEscapedCommonJSTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.kt"); + } + + @Test + @TestMetadata("externalEscapedTopLevel.kt") + public void testExternalEscapedTopLevel() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.kt"); + } + + @Test + @TestMetadata("topLevelExportedClass.kt") + public void testTopLevelExportedClass() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedClass.kt"); + } + + @Test + @TestMetadata("topLevelExportedCompanion.kt") + public void testTopLevelExportedCompanion() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedCompanion.kt"); + } + + @Test + @TestMetadata("topLevelExportedFunction.kt") + public void testTopLevelExportedFunction() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt"); + } + + @Test + @TestMetadata("topLevelExportedVariable.kt") + public void testTopLevelExportedVariable() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelExportedVariable.kt"); + } + + @Test + @TestMetadata("topLevelLocalClassMangling.kt") + public void testTopLevelLocalClassMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalClassMangling.kt"); + } + + @Test + @TestMetadata("topLevelLocalCompanionMangling.kt") + public void testTopLevelLocalCompanionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalCompanionMangling.kt"); + } + + @Test + @TestMetadata("topLevelLocalFunctionMangling.kt") + public void testTopLevelLocalFunctionMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalFunctionMangling.kt"); + } + + @Test + @TestMetadata("topLevelLocalVariableMangling.kt") + public void testTopLevelLocalVariableMangling() throws Exception { + runTest("js/js.translator/testData/box/escapedIdentifiers/topLevelLocalVariableMangling.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/examples") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index ec5eda34840..57d17f5a77e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -31,9 +31,9 @@ import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties; import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind; import org.jetbrains.kotlin.js.backend.ast.metadata.SpecialFunction; +import org.jetbrains.kotlin.js.common.IdentifierPolicyKt; import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.naming.NameSuggestion; -import org.jetbrains.kotlin.js.naming.NameSuggestionKt; import org.jetbrains.kotlin.js.naming.SuggestedName; import org.jetbrains.kotlin.js.resolve.diagnostics.JsBuiltinNameClashChecker; import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver; @@ -843,7 +843,7 @@ public final class StaticContext { JsExpression currentImports = pureFqn(getNameForImportsForInline(), null); JsExpression lhsModuleRef; - if (NameSuggestionKt.isValidES5Identifier(moduleId)) { + if (IdentifierPolicyKt.isValidES5Identifier(moduleId)) { moduleRef = pureFqn(moduleId, importsRef); lhsModuleRef = pureFqn(moduleId, currentImports); } diff --git a/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberClassMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberClassMangling.kt new file mode 100644 index 00000000000..7a5211b1826 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberClassMangling.kt @@ -0,0 +1,23 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +@JsExport() +class A { + class `$invalid inner` {} +} + +class B { + class `$invalid inner` {} +} + +fun box(): String { + // DCE preventing + val b = B() + + assertEquals("function", js("typeof A['\$invalid inner']")) + assertEquals(js("undefined"), js("B['\$invalid inner']")) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFieldMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFieldMangling.kt new file mode 100644 index 00000000000..b216972e0c4 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFieldMangling.kt @@ -0,0 +1,47 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +@JsExport() +class A { + val `#invalid@char value`: Int = 41 + val __invalid_char_value: Int = 23 + + var `--invalud char@var`: String = "A: before" +} + +class B { + val `#invalid@char value`: Int = 42 + val __invalid_char_value: Int = 24 + + var `--invalud char@var`: String = "B: before" +} + +fun box(): String { + val a = A() + val b = B() + + assertEquals(23, a.__invalid_char_value) + assertEquals(24, b.__invalid_char_value) + + assertEquals(41, a.`#invalid@char value`) + assertEquals(42, b.`#invalid@char value`) + + assertEquals("A: before", a.`--invalud char@var`) + assertEquals("B: before", b.`--invalud char@var`) + + a.`--invalud char@var` = "A: after" + b.`--invalud char@var` = "B: after" + + assertEquals("A: after", a.`--invalud char@var`) + assertEquals("B: after", b.`--invalud char@var`) + + assertEquals(41, js("a['#invalid@char value']")) + assertEquals(js("undefined"), js("b['#invalid@char value']")) + + assertEquals("A: after", js("a['--invalud char@var']")) + assertEquals(js("undefined"), js("b['--invalud char@var']")) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFunctionMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFunctionMangling.kt new file mode 100644 index 00000000000..83d95e1fbc3 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/classLikeMemberFunctionMangling.kt @@ -0,0 +1,47 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +interface IA { + fun `run•invalid@test`(): Int + fun `run@invalid@test`(): Int + fun run_invalid_test(): Int +} + +@JsExport() +class A : IA { + override fun `run•invalid@test`(): Int = 41 + override fun `run@invalid@test`(): Int = 34 + override fun run_invalid_test(): Int = 23 +} + +class B : IA { + override fun `run•invalid@test`(): Int = 42 + override fun `run@invalid@test`(): Int = 35 + override fun run_invalid_test(): Int = 24 +} + +fun box(): String { + val a: IA = A() + val b: IA = B() + + assertEquals(23, a.run_invalid_test()) + assertEquals(24, b.run_invalid_test()) + + assertEquals(34, a.`run@invalid@test`()) + assertEquals(35, b.`run@invalid@test`()) + + assertEquals(41, a.`run•invalid@test`()) + assertEquals(42, b.`run•invalid@test`()) + + assertEquals("function", js("typeof a['run•invalid@test']")) + assertEquals(41, js("a['run•invalid@test']()")) + assertEquals(js("undefined"), js("b['run•invalid@test']")) + + assertEquals("function", js("typeof a['run@invalid@test']")) + assertEquals(34, js("a['run@invalid@test']()")) + assertEquals(js("undefined"), js("b['run@invalid@test']")) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/dynamicEscapedField.kt b/js/js.translator/testData/box/escapedIdentifiers/dynamicEscapedField.kt new file mode 100644 index 00000000000..1e579b23e6a --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/dynamicEscapedField.kt @@ -0,0 +1,11 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +fun box(): String { + val a: dynamic = js("{ \"--invalid--property@\": 42 }") + assertEquals(42, a.`--invalid--property@`) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.js b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.js new file mode 100644 index 00000000000..5cffd74218d --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.js @@ -0,0 +1,11 @@ +define("lib", [], function() { + return { + "@get something-invalid"() { + return "something invalid" + }, + "some+value": 42, + "+some+object%:": { + foo: "%%++%%" + } + }; +}); \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.kt b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.kt new file mode 100644 index 00000000000..1e3d79c2b9c --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedAMDTopLevel.kt @@ -0,0 +1,28 @@ +// IGNORE_BACKEND: JS +// MODULE_KIND: COMMON_JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +// FILE: lib.kt +@file:JsModule("lib") +package lib + +external fun `@get something-invalid`(): String = definedExternally + +external val `some+value`: Int = definedExternally + +external object `+some+object%:` { + val foo: String = definedExternally +} + +// FILE: main.kt +import lib.`some+value` +import lib.`@get something-invalid` +import lib.`+some+object%:` + +fun box(): String { + assertEquals(42, `some+value`) + assertEquals("%%++%%", `+some+object%:`.foo) + assertEquals("something invalid", `@get something-invalid`()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.js b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.js new file mode 100644 index 00000000000..2de1cd1fb6d --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.js @@ -0,0 +1,15 @@ +function A() { + this["@invalid @ val@"] = 23 + this["--invalid-var"] = "A: before" +} + +A.prototype["get something$weird"] = function() { + return "something weird" +} + +A["static val"] = 42 +A["static var"] = "Companion: before" + +A["get 🦄"] = function() { + return "🦄" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.kt b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.kt new file mode 100644 index 00000000000..bbc0f52c7de --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedClassFields.kt @@ -0,0 +1,39 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +external class A { + val `@invalid @ val@`: Int = definedExternally + var `--invalid-var`: String = definedExternally + + fun `get something$weird`(): String = definedExternally + + companion object { + val `static val`: Int = definedExternally + var `static var`: String = definedExternally + + fun `get 🦄`(): String = definedExternally + } +} + +fun box(): String { + val a = A() + + assertEquals(23, a.`@invalid @ val@`) + assertEquals("A: before", a.`--invalid-var`) + assertEquals("something weird", a.`get something$weird`()) + + a.`--invalid-var` = "A: after" + assertEquals("A: after", a.`--invalid-var`) + + assertEquals(42, A.Companion.`static val`) + assertEquals("Companion: before", A.Companion.`static var`) + assertEquals("\uD83E\uDD84", A.Companion.`get 🦄`()) + + A.`static var` = "Companion: after" + + assertEquals("Companion: after", A.Companion.`static var`) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.js b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.js new file mode 100644 index 00000000000..2ad791941ea --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.js @@ -0,0 +1,11 @@ +$kotlin_test_internal$.beginModule("lib"); +module.exports = { + "@get something-invalid"() { + return "something invalid" + }, + "some+value": 42, + "+some+object%:": { + foo: "%%++%%" + } +} +$kotlin_test_internal$.endModule("lib"); \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.kt b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.kt new file mode 100644 index 00000000000..1e3d79c2b9c --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedCommonJSTopLevel.kt @@ -0,0 +1,28 @@ +// IGNORE_BACKEND: JS +// MODULE_KIND: COMMON_JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +// FILE: lib.kt +@file:JsModule("lib") +package lib + +external fun `@get something-invalid`(): String = definedExternally + +external val `some+value`: Int = definedExternally + +external object `+some+object%:` { + val foo: String = definedExternally +} + +// FILE: main.kt +import lib.`some+value` +import lib.`@get something-invalid` +import lib.`+some+object%:` + +fun box(): String { + assertEquals(42, `some+value`) + assertEquals("%%++%%", `+some+object%:`.foo) + assertEquals("something invalid", `@get something-invalid`()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.js b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.js new file mode 100644 index 00000000000..b61776df451 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.js @@ -0,0 +1,9 @@ +this["@get something-invalid"] = function() { + return "something invalid" +} + +this["some+value"] = 42 + +this["+some+object%:"] = { + foo: "%%++%%" +} diff --git a/js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.kt b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.kt new file mode 100644 index 00000000000..bb1c6422896 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/externalEscapedTopLevel.kt @@ -0,0 +1,23 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +external fun `@get something-invalid`(): String = definedExternally + +external var `some+value`: Int + get() = definedExternally + set(a: Int) = definedExternally + +external object `+some+object%:` { + val foo: String = definedExternally +} + +fun box(): String { + val a = js("2") + assertEquals(42, `some+value`) + assertEquals("%%++%%", `+some+object%:`.foo) + assertEquals("something invalid", `@get something-invalid`()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedClass.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedClass.kt new file mode 100644 index 00000000000..1cb65937bda --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedClass.kt @@ -0,0 +1,24 @@ +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +// MODULE: export_invalid_name_class +// FILE: lib.kt + +@JsExport +class `invalid@class-name `() { + fun foo(): Int = 42 +} + +// FILE: test.js +function box() { + var InvalidClass = this["export_invalid_name_class"]["invalid@class-name "] + var instance = new InvalidClass() + var value = instance.foo() + + if (value !== 42) + return "false: expect exproted class function to return 42 but it equals " + value + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedCompanion.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedCompanion.kt new file mode 100644 index 00000000000..bcc539bd96d --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedCompanion.kt @@ -0,0 +1,30 @@ +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +// MODULE: export_invalid_name_class +// FILE: lib.kt + +@JsExport +class `invalid@class-name `() { + companion object { + val `@invalid val@`: Int = 23 + fun `invalid fun`(): Int = 42 + } +} + +// FILE: test.js +function box() { + var InvalidClass = this["export_invalid_name_class"]["invalid@class-name "] + + if (InvalidClass.Companion["@invalid val@"] !== 23) + return "false: expect exproted class static variable '@invalid val@' to be 23 but it equals " + InvalidClass.Companion["@invalud val@"] + + var result = InvalidClass.Companion["invalid fun"]() + + if (result !== 42) + return "false: expect exproted class static function 'invalid fun' to return 23 but it equals " + result + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt new file mode 100644 index 00000000000..267673b4cf0 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedFunction.kt @@ -0,0 +1,20 @@ +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +// MODULE: export_invalid_name_function +// FILE: lib.kt + +@JsExport +fun `@do something like-this`(): Int = 42 + +// FILE: test.js +function box() { + var value = this["export_invalid_name_function"]["@do something like-this"]() + + if (value !== 42) + return "false: expect exproted function '@do something like-this' to return 42 but it equals " + value + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedVariable.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedVariable.kt new file mode 100644 index 00000000000..b1d4e4b0bd3 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelExportedVariable.kt @@ -0,0 +1,20 @@ +// IGNORE_BACKEND: JS +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +// MODULE: export_invalid_name_variable +// FILE: lib.kt + +@JsExport +val `my@invalid variable` = 42 + +// FILE: test.js +function box() { + var variableValue = this["export_invalid_name_variable"]["my@invalid variable"] + + if (variableValue !== 42) + return "false: expect exproted 'my@invalid variable' to be 42 but it equals " + variableValue + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalClassMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalClassMangling.kt new file mode 100644 index 00000000000..1fc8454a317 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalClassMangling.kt @@ -0,0 +1,25 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +class class_with_invalid_chars { + fun foo(): Int = 23 +} + +class `class@with$invalid chars` { + fun foo(): Int = 42 +} + +fun box(): String { + val a = class_with_invalid_chars() + val b = `class@with$invalid chars`() + + assertEquals(true, a is class_with_invalid_chars) + assertEquals(true, b is `class@with$invalid chars`) + + assertEquals(23, a.foo()) + assertEquals(42, b.foo()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalCompanionMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalCompanionMangling.kt new file mode 100644 index 00000000000..4a1709f675c --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalCompanionMangling.kt @@ -0,0 +1,23 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +class class_with_invalid_chars { + companion object { + fun foo(): Int = 23 + } +} + +class `class@with$invalid chars` { + companion object { + fun foo(): Int = 42 + } +} + +fun box(): String { + assertEquals(23, class_with_invalid_chars.foo()) + assertEquals(42, `class@with$invalid chars`.foo()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalFunctionMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalFunctionMangling.kt new file mode 100644 index 00000000000..4c1055a8055 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalFunctionMangling.kt @@ -0,0 +1,20 @@ +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +fun _my_fn(a: Int): Int { return a } +fun `my fn`(): Int { return 42 } + +fun box(): String { + val fn1 = ::_my_fn + val fn2 = ::`my fn` + + assertEquals("_my_fn", fn1.name) + assertEquals("my fn", fn2.name) + + assertEquals(23, _my_fn(23)) + assertEquals(42, `my fn`()) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalVariableMangling.kt b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalVariableMangling.kt new file mode 100644 index 00000000000..42f7a2ec2c5 --- /dev/null +++ b/js/js.translator/testData/box/escapedIdentifiers/topLevelLocalVariableMangling.kt @@ -0,0 +1,13 @@ +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping + +package foo + +val _my_invalid_variable = 23 +val `my@invalid variable` = 42 + +fun box(): String { + assertEquals(23, _my_invalid_variable) + assertEquals(42, `my@invalid variable`) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/inline/privateProperty.kt b/js/js.translator/testData/box/inline/privateProperty.kt index 0cdf20fd2c2..80b3ecbfa4f 100644 --- a/js/js.translator/testData/box/inline/privateProperty.kt +++ b/js/js.translator/testData/box/inline/privateProperty.kt @@ -7,10 +7,10 @@ var a = 0 // CHECK_NOT_CALLED_IN_SCOPE: function=get_p1 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p1 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p1 scope=box TARGET_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _get_p1_ IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _get_p1__59117 IGNORED_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=_get_p1_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p1_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p1_ scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p1__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p1__59117 scope=box IGNORED_BACKENDS=JS private inline var p1: Int get() = a + 10000 set(v) { @@ -20,9 +20,9 @@ private inline var p1: Int // CHECK_FUNCTION_EXISTS: get_p2 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=get_p2 scope=box TARGET_BACKENDS=JS // CHECK_CALLED_IN_SCOPE: function=set_p2 scope=box TARGET_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _get_p2_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p2_ scope=box IGNORED_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_set_p2_ scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _get_p2__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p2__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_set_p2__59117 scope=box IGNORED_BACKENDS=JS private var p2: Int inline get() = a + 20000 set(v) { @@ -32,9 +32,9 @@ private var p2: Int // CHECK_CALLED_IN_SCOPE: function=get_p3 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p3 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p3 scope=box TARGET_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_get_p3_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p3_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p3_ scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_get_p3__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p3__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p3__59117 scope=box IGNORED_BACKENDS=JS var p3: Int get() = a + 30000 private inline set(v) { @@ -44,9 +44,9 @@ var p3: Int // CHECK_CALLED_IN_SCOPE: function=get_p4 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p4 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p4 scope=box TARGET_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_get_p4_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p4_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p4_ scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_get_p4__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p4__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p4__59117 scope=box IGNORED_BACKENDS=JS private var p4: Int get() = a + 40000 inline set(v) { @@ -57,10 +57,10 @@ private var p4: Int // CHECK_NOT_CALLED_IN_SCOPE: function=get_p5 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p5 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p5 scope=box TARGET_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _get_p5_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p5_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p5_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p5_ scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _get_p5__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p5__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p5__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p5__59117 scope=box IGNORED_BACKENDS=JS private inline var Int.p5: Int get() = this * 100 + a + 50000 set(v) { @@ -70,9 +70,9 @@ private inline var Int.p5: Int // CHECK_FUNCTION_EXISTS: get_p6 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=get_p6 scope=box TARGET_BACKENDS=JS // CHECK_CALLED_IN_SCOPE: function=set_p6 scope=box TARGET_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _get_p6_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p6_ scope=box IGNORED_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_set_p6_ scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _get_p6__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p6__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_set_p6__59117 scope=box IGNORED_BACKENDS=JS private var Int.p6: Int inline get() = this * 100 + a + 60000 set(v) { @@ -82,9 +82,9 @@ private var Int.p6: Int // CHECK_CALLED_IN_SCOPE: function=get_p7 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p7 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p7 scope=box TARGET_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_get_p7_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p7_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p7_ scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_get_p7__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p7__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p7__59117 scope=box IGNORED_BACKENDS=JS var Int.p7: Int get() = this * 100 + a + 70000 private inline set(v) { @@ -94,9 +94,9 @@ var Int.p7: Int // CHECK_CALLED_IN_SCOPE: function=get_p8 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p8 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p8 scope=box TARGET_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_get_p8_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p8_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p8_ scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_get_p8__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p8__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p8__59117 scope=box IGNORED_BACKENDS=JS private var Int.p8: Int get() = this * 100 + a + 80000 inline set(v) { @@ -160,10 +160,10 @@ private class A { // CHECK_NOT_CALLED_IN_SCOPE: function=get_p15 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p15 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p15 scope=box TARGET_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _get_p15_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p15_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p15_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p15_ scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _get_p15__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p15__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p15__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p15__59117 scope=box IGNORED_BACKENDS=JS private inline var A.p15: Int get() = a + 150000 set(v) { @@ -173,9 +173,9 @@ private inline var A.p15: Int // CHECK_FUNCTION_EXISTS: get_p16 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=get_p16 scope=box TARGET_BACKENDS=JS // CHECK_CALLED_IN_SCOPE: function=set_p16 scope=box TARGET_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _get_p16_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p16_ scope=box IGNORED_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_set_p16_ scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _get_p16__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_get_p16__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_set_p16__59117 scope=box IGNORED_BACKENDS=JS private var A.p16: Int inline get() = a + 160000 set(v) { @@ -185,9 +185,9 @@ private var A.p16: Int // CHECK_CALLED_IN_SCOPE: function=get_p17 scope=box TARGET_BACKENDS=JS // CHECK_FUNCTION_EXISTS: set_p17 TARGET_BACKENDS=JS // CHECK_NOT_CALLED_IN_SCOPE: function=set_p17 scope=box TARGET_BACKENDS=JS -// CHECK_CALLED_IN_SCOPE: function=_get_p17_ scope=box IGNORED_BACKENDS=JS -// CHECK_FUNCTION_EXISTS: _set_p17_ IGNORED_BACKENDS=JS -// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p17_ scope=box IGNORED_BACKENDS=JS +// CHECK_CALLED_IN_SCOPE: function=_get_p17__59117 scope=box IGNORED_BACKENDS=JS +// CHECK_FUNCTION_EXISTS: _set_p17__59117 IGNORED_BACKENDS=JS +// CHECK_NOT_CALLED_IN_SCOPE: function=_set_p17__59117 scope=box IGNORED_BACKENDS=JS private var A.p17: Int get() = a + 170000 inline set(v) { diff --git a/js/js.translator/testData/typescript-export/declarations/declarations__main.js b/js/js.translator/testData/typescript-export/declarations/declarations__main.js index 584970664d2..ddd572d2ee9 100644 --- a/js/js.translator/testData/typescript-export/declarations/declarations__main.js +++ b/js/js.translator/testData/typescript-export/declarations/declarations__main.js @@ -22,6 +22,10 @@ var _valCustomWithField = JS_TESTS.foo._valCustomWithField; var A4 = JS_TESTS.foo.A4; var O = JS_TESTS.foo.O; var takesO = JS_TESTS.foo.takesO; +var KT_37829 = JS_TESTS.foo.KT_37829; +var TestSealed = JS_TESTS.foo.TestSealed; +var TestAbstract = JS_TESTS.foo.TestAbstract; +var TestDataClass = JS_TESTS.foo.TestDataClass; function assert(condition) { if (!condition) { throw "Assertion failed"; @@ -80,5 +84,15 @@ function box() { assert(O.x === 10); assert(O.foo() === 20); assert(takesO(O) === 30); + assert(KT_37829.Companion.x == 10); + assert(new TestSealed.AA().name == "AA"); + assert(new TestSealed.AA().bar() == "bar"); + assert(new TestSealed.BB().name == "BB"); + assert(new TestSealed.BB().baz() == "baz"); + assert(new TestAbstract.AA().name == "AA"); + assert(new TestAbstract.AA().bar() == "bar"); + assert(new TestAbstract.BB().name == "BB"); + assert(new TestAbstract.BB().baz() == "baz"); + assert(new TestDataClass.Nested().prop == "hello"); return "OK"; } diff --git a/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts new file mode 100644 index 00000000000..690ddbf28e8 --- /dev/null +++ b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.d.ts @@ -0,0 +1,31 @@ +declare namespace JS_TESTS { + type Nullable = T | null | undefined + namespace foo { + + + + function invalid_args_name_sum(first_value: number, second_value: number): number; + + class A1 { + constructor(first_value: number, second_value: number); + readonly "first value": number; + "second.value": number; + } + class A2 { + constructor(); + "invalid:name": number; + } + class A3 { + constructor(); + "invalid@name sum"(x: number, y: number): number; + invalid_args_name_sum(first_value: number, second_value: number): number; + } + class A4 { + constructor(); + static readonly Companion: { + "@invalid+name@": number; + "^)run.something.weird^("(): string; + }; + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.kt b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.kt new file mode 100644 index 00000000000..c18754dcc92 --- /dev/null +++ b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations.kt @@ -0,0 +1,46 @@ +// CHECK_TYPESCRIPT_DECLARATIONS +// RUN_PLAIN_BOX_FUNCTION +// SKIP_MINIFICATION +// SKIP_NODE_JS +// IGNORE_BACKEND: JS +// !LANGUAGE: +JsAllowInvalidCharsIdentifiersEscaping +// INFER_MAIN_MODULE +// MODULE: JS_TESTS +// FILE: escapedDeclarations.kt + +@file:JsExport + +package foo + +fun `invalid@name sum`(x: Int, y: Int): Int = + x + y + +fun invalid_args_name_sum(`first value`: Int, `second value`: Int): Int = + `first value` + `second value` + +// Properties + +val `invalid name val`: Int = 1 +var `invalid@name var`: Int = 1 + +// Classes + +class `Invalid A` +class A1(val `first value`: Int, var `second.value`: Int) +class A2 { + var `invalid:name`: Int = 42 +} +class A3 { + fun `invalid@name sum`(x: Int, y: Int): Int = + x + y + + fun invalid_args_name_sum(`first value`: Int, `second value`: Int): Int = + `first value` + `second value` +} + +class A4 { + companion object { + var `@invalid+name@` = 23 + fun `^)run.something.weird^(`(): String = ")_(" + } +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations__main.js b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations__main.js new file mode 100644 index 00000000000..a9f68a6ae6c --- /dev/null +++ b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations__main.js @@ -0,0 +1,29 @@ +"use strict"; +var foo = JS_TESTS.foo; +var A1 = JS_TESTS.foo.A1; +var A2 = JS_TESTS.foo.A2; +var A3 = JS_TESTS.foo.A3; +var A4 = JS_TESTS.foo.A4; +function assert(condition) { + if (!condition) { + throw "Assertion failed"; + } +} +function box() { + assert(foo.invalid_args_name_sum(10, 20) === 30); + assert(foo["invalid@name sum"](10, 20) === 30); + assert(foo["invalid name val"] === 1); + assert(foo["invalid@name var"] === 1); + foo["invalid@name var"] = 4; + assert(foo["invalid@name var"] === 4); + new foo["Invalid A"](); + assert(new A1(10, 20)["first value"] === 10); + assert(new A1(10, 20)["second.value"] === 20); + assert(new A2()["invalid:name"] === 42); + var a3 = new A3(); + assert(a3.invalid_args_name_sum(10, 20) === 30); + assert(a3["invalid@name sum"](10, 20) === 30); + assert(A4.Companion["@invalid+name@"] == 23); + assert(A4.Companion["^)run.something.weird^("]() === ")_("); + return "OK"; +} diff --git a/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations__main.ts b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations__main.ts new file mode 100644 index 00000000000..3cf8eedc13d --- /dev/null +++ b/js/js.translator/testData/typescript-export/escapedDeclarations/escapedDeclarations__main.ts @@ -0,0 +1,37 @@ +import foo = JS_TESTS.foo; +import A1 = JS_TESTS.foo.A1; +import A2 = JS_TESTS.foo.A2; +import A3 = JS_TESTS.foo.A3; +import A4 = JS_TESTS.foo.A4; + +function assert(condition: boolean) { + if (!condition) { + throw "Assertion failed"; + } +} + +function box(): string { + assert(foo.invalid_args_name_sum(10, 20) === 30); + assert((foo as any)["invalid@name sum"](10, 20) === 30); + + assert((foo as any)["invalid name val"] === 1); + assert((foo as any)["invalid@name var"] === 1); + (foo as any)["invalid@name var"] = 4 + assert((foo as any)["invalid@name var"] === 4); + + new (foo as any)["Invalid A"](); + + assert(new A1(10, 20)["first value"] === 10); + assert(new A1(10, 20)["second.value"] === 20); + + assert(new A2()["invalid:name"] === 42); + + const a3 = new A3() + assert(a3.invalid_args_name_sum(10, 20) === 30); + assert(a3["invalid@name sum"](10, 20) === 30); + + assert(A4.Companion["@invalid+name@"] == 23); + assert(A4.Companion["^)run.something.weird^("]() === ")_("); + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/typescript-export/escapedDeclarations/tsconfig.json b/js/js.translator/testData/typescript-export/escapedDeclarations/tsconfig.json new file mode 100644 index 00000000000..17612c56c9d --- /dev/null +++ b/js/js.translator/testData/typescript-export/escapedDeclarations/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [ "./*" ], + "extends": "../common.tsconfig.json" +} \ No newline at end of file