Fix handling non-ES5 identifiers in JS

When a Kotlin identifiers contains non-ES5 chars, JS BE
either reports error (for published declarations) or mangles name
(for non-published ones). The old approach relied on wrong
assuption that Java identifier = ES identifier.
However, that's not true. This commit introduces functions that
check identifiers according to ES5.1 spec rather than
using Character.isIdentifierStart[Part]

See KT-17476
This commit is contained in:
Alexey Andreev
2017-05-25 14:35:49 +03:00
committed by Alexey Andreev
parent 24c0a1e7ce
commit a0ddbf0e9b
2 changed files with 26 additions and 6 deletions
@@ -294,7 +294,7 @@ class NameSuggestion {
// Make all public declarations stable
effectiveVisibility == Visibilities.PUBLIC -> mangledAndStable()
descriptor is CallableMemberDescriptor && descriptor.isOverridableOrOverrides -> mangledAndStable()
descriptor.isOverridableOrOverrides -> mangledAndStable()
// Make all protected declarations of non-final public classes stable
effectiveVisibility == Visibilities.PROTECTED &&
@@ -346,8 +346,28 @@ class NameSuggestion {
@JvmStatic fun sanitizeName(name: String): String {
if (name.isEmpty()) return "_"
val first = name.first().let { if (java.lang.Character.isJavaIdentifierStart(it)) it else '_' }
return first.toString() + name.drop(1).map { if (java.lang.Character.isJavaIdentifierPart(it)) it else '_' }.joinToString("")
val first = name.first().let { if (it.isES5IdentifierStart()) it else '_' }
return first.toString() + name.drop(1).map { if (it.isES5IdentifierPart()) it else '_' }.joinToString("")
}
// See ES 5.1 spec: https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
private fun Char.isES5IdentifierStart() =
Character.isLetter(this) || // Lu | Ll | Lt | Lm | Lo
Character.getType(this).toByte() == Character.LETTER_NUMBER ||
// Nl which is missing in Character.isLetter, but present in UnicodeLetter in spec
this == '_' ||
this == '$'
private fun Char.isES5IdentifierPart() =
isES5IdentifierStart() ||
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
}
}
@@ -23,9 +23,9 @@ fun test3(): String {
}
fun test4(): String {
val `()` = "OK"
fun `[]`() = `()`
return `[]`()
val `1(¢)` = "OK"
fun `[£]`() = `1(¢)`
return `[£]`()
}
fun box(): String {