[FIR] Implement FirNativeIdentifierChecker

* INVALID_CHARACTERS_NATIVE
This commit is contained in:
Ivan Kochurkin
2022-07-03 22:33:02 +03:00
parent a44d5bf52f
commit 0af43757e3
8 changed files with 131 additions and 27 deletions
@@ -5,8 +5,11 @@
package org.jetbrains.kotlin.fir.checkers.generator.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.DiagnosticList
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.PositioningStrategy
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
@@ -30,5 +33,11 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") {
val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL by error<KtElement>()
val INAPPLICABLE_THREAD_LOCAL by error<KtElement>()
val INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL by error<KtElement>()
val INVALID_CHARACTERS_NATIVE by deprecationError<PsiElement>(
LanguageFeature.ProhibitInvalidCharsInNativeIdentifiers,
PositioningStrategy.NAME_IDENTIFIER
) {
parameter<String>("message")
}
}
}
@@ -5,7 +5,10 @@
package org.jetbrains.kotlin.fir.analysis.diagnostics.native
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitInvalidCharsInNativeIdentifiers
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
@@ -28,6 +31,7 @@ object FirNativeErrors {
val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL by error0<KtElement>()
val INAPPLICABLE_THREAD_LOCAL by error0<KtElement>()
val INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL by error0<KtElement>()
val INVALID_CHARACTERS_NATIVE by deprecationError1<PsiElement, String>(ProhibitInvalidCharsInNativeIdentifiers, SourceElementPositioningStrategies.NAME_IDENTIFIER)
init {
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAP
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_INHERITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_CHARACTERS_NATIVE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY
@@ -39,6 +40,7 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"@ThreadLocal is applicable only to property with backing field, to property with delegation or to objects"
)
map.put(INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL, "@ThreadLocal is applicable only to top level declarations")
map.put(INVALID_CHARACTERS_NATIVE, "Name {0}", TO_STRING)
map.checkMissingMessages(FirNativeErrors)
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.native.checkers
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.name.Name
object FirNativeIdentifierChecker : FirBasicDeclarationChecker() {
// Also includes characters used by IR mangler (see MangleConstant).
private val invalidChars = setOf(
'.', ';', ',', '(', ')', '[', ']', '{', '}', '/', '<', '>',
':', '\\', '$', '&', '~', '*', '?', '#', '|', '§', '%', '@',
)
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val source = declaration.source
when (declaration) {
is FirRegularClass -> checkNameAndReport(declaration.name, source, context, reporter)
is FirSimpleFunction -> checkNameAndReport(declaration.name, source, context, reporter)
is FirTypeParameter -> checkNameAndReport(declaration.name, source, context, reporter)
is FirProperty -> checkNameAndReport(declaration.name, source, context, reporter)
is FirTypeAlias -> checkNameAndReport(declaration.name, source, context, reporter)
is FirValueParameter -> checkNameAndReport(declaration.name, source, context, reporter)
is FirEnumEntry -> checkNameAndReport(declaration.name, source, context, reporter)
else -> return
}
}
private fun checkNameAndReport(name: Name, source: KtSourceElement?, context: CheckerContext, reporter: DiagnosticReporter) {
if (source != null && source.kind !is KtFakeSourceElementKind && !name.isSpecial) {
val text = name.asString()
val message = when {
text.isEmpty() -> "should not be empty"
text.any { it in invalidChars } -> "contains illegal characters: " +
invalidChars.intersect(text.toSet()).joinToString("", prefix = "\"", postfix = "\"")
else -> null
}
if (message != null) {
reporter.reportOn(source, FirNativeErrors.INVALID_CHARACTERS_NATIVE, message, context)
}
}
}
}
@@ -13,6 +13,7 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
get() = setOf(
FirNativeThrowsChecker,
FirNativeSharedImmutableChecker,
FirNativeThreadLocalChecker
FirNativeThreadLocalChecker,
FirNativeIdentifierChecker
)
}
@@ -855,6 +855,14 @@ object LightTreePositioningStrategies {
if (node.tokenType == KtNodeTypes.LABEL_QUALIFIER) {
return super.mark(node, startOffset, endOffset - 1, tree)
}
if (node.tokenType == KtNodeTypes.PACKAGE_DIRECTIVE) {
val referenceExpression = tree.findLastDescendant(node) {
it.tokenType == KtNodeTypes.REFERENCE_EXPRESSION
}
if (referenceExpression != null) {
return markElement(referenceExpression, startOffset, endOffset, tree, node)
}
}
return DEFAULT.mark(node, startOffset, endOffset, tree)
}
}
@@ -1449,8 +1457,28 @@ fun FlyweightCapableTreeStructure<LighterASTNode>.findFirstDescendant(
): LighterASTNode? {
val childrenRef = Ref<Array<LighterASTNode?>>()
getChildren(node, childrenRef)
return childrenRef.get()?.firstOrNull { it != null && predicate(it) }
?: childrenRef.get()?.firstNotNullOfOrNull { child -> child?.let { findFirstDescendant(it, predicate) } }
val nodes = childrenRef.get()
return nodes?.firstOrNull { it != null && predicate(it) }
?: nodes?.firstNotNullOfOrNull { child -> child?.let { findFirstDescendant(it, predicate) } }
}
fun FlyweightCapableTreeStructure<LighterASTNode>.findLastDescendant(
node: LighterASTNode,
predicate: (LighterASTNode) -> Boolean
): LighterASTNode? {
val childrenRef = Ref<Array<LighterASTNode?>>()
getChildren(node, childrenRef)
val nodes = childrenRef.get()
return nodes?.lastOrNull { it != null && predicate(it) }
?: run {
for (child in nodes.reversed()) {
val result = child?.let { findLastDescendant(it, predicate) }
if (result != null) {
return result
}
}
return null
}
}
fun FlyweightCapableTreeStructure<LighterASTNode>.collectDescendantsOfType(
@@ -847,6 +847,11 @@ object PositioningStrategies {
}
} else if (element is KtLabelReferenceExpression) {
return super.mark(element.getReferencedNameElement())
} else if (element is KtPackageDirective) {
val nameIdentifier = element.nameIdentifier
if (nameIdentifier != null) {
return super.mark(nameIdentifier)
}
}
return DEFAULT.mark(element)
+24 -24
View File
@@ -8,61 +8,61 @@ package `check.pkg`
// FILE: 2.kt
package totally.normal.pkg
class `Check.Class`
class <!INVALID_CHARACTERS_NATIVE_ERROR!>`Check.Class`<!>
class NormalClass {
fun `check$member`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check$member`<!>() {}
}
object `Check;Object`
object <!INVALID_CHARACTERS_NATIVE_ERROR!>`Check;Object`<!>
object NormalObject
data class Pair(val first: Int, val `next,one`: Int)
data class Pair(val first: Int, val <!INVALID_CHARACTERS_NATIVE_ERROR!>`next,one`<!>: Int)
object Delegate {
operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): Any? = null
}
fun `check(function`() {
val `check)variable` = 1
val `check[delegated[variable` by Delegate
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check(function`<!>() {
val <!INVALID_CHARACTERS_NATIVE_ERROR!>`check)variable`<!> = 1
val <!INVALID_CHARACTERS_NATIVE_ERROR!>`check[delegated[variable`<!> by Delegate
val normalVariable = 2
val normalDelegatedVariable by Delegate
val (check, `destructuring]declaration`) = Pair(1, 2)
val (check, <!INVALID_CHARACTERS_NATIVE_ERROR!>`destructuring]declaration`<!>) = Pair(1, 2)
}
fun normalFunction() {}
val `check{property` = 1
val `check}delegated}property` by Delegate
val <!INVALID_CHARACTERS_NATIVE_ERROR!>`check{property`<!> = 1
val <!INVALID_CHARACTERS_NATIVE_ERROR!>`check}delegated}property`<!> by Delegate
val normalProperty = 2
val normalDelegatedProperty by Delegate
fun checkValueParameter(`check/parameter`: Int) {}
fun checkValueParameter(<!INVALID_CHARACTERS_NATIVE_ERROR!>`check/parameter`<!>: Int) {}
fun <`check<type<parameter`, normalTypeParameter> checkTypeParameter() {}
fun <<!INVALID_CHARACTERS_NATIVE_ERROR!>`check<type<parameter`<!>, normalTypeParameter> checkTypeParameter() {}
enum class `Check>Enum>Entry` {
`CHECK:ENUM:ENTRY`;
enum class <!INVALID_CHARACTERS_NATIVE_ERROR!>`Check>Enum>Entry`<!> {
<!INVALID_CHARACTERS_NATIVE_ERROR!>`CHECK:ENUM:ENTRY`<!>;
}
typealias `check\typealias` = Any
typealias <!INVALID_CHARACTERS_NATIVE_ERROR!>`check\typealias`<!> = Any
fun `check&`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check&`<!>() {}
fun `check~`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check~`<!>() {}
fun `check*`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check*`<!>() {}
fun `check?`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check?`<!>() {}
fun `check#`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check#`<!>() {}
fun `check|`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check|`<!>() {}
fun `check§`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check§`<!>() {}
fun `check%`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check%`<!>() {}
fun `check@`() {}
fun <!INVALID_CHARACTERS_NATIVE_ERROR!>`check@`<!>() {}