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

This commit is contained in:
Artem Kobzar
2021-10-29 17:55:59 +00:00
committed by Space
parent ea10b4a781
commit 979e9f94ef
59 changed files with 1260 additions and 159 deletions
@@ -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
}
@@ -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<JsCallChecker>()
container.useImpl<JsTypeSpecificityComparator>()
container.useImpl<JsNameClashChecker>()
container.useImpl<JsIdentifierChecker>()
container.useImpl<JsNameCharsChecker>()
container.useImpl<JsBuiltinNameClashChecker>()
container.useInstance(JsModuleClassLiteralChecker)
@@ -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))
}
@@ -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 }
@@ -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