[FIR JS] Implement FirJsNameChecker

This commit is contained in:
Nikolay Lunyak
2023-01-04 17:17:04 +02:00
committed by Space Team
parent 2f9831ed99
commit 5817155616
21 changed files with 208 additions and 46 deletions
@@ -1060,6 +1060,42 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJsErrors.JS_NAME_IS_NOT_ON_ALL_ACCESSORS) { firDiagnostic ->
JsNameIsNotOnAllAccessorsImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.JS_NAME_PROHIBITED_FOR_NAMED_NATIVE) { firDiagnostic ->
JsNameProhibitedForNamedNativeImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.JS_NAME_PROHIBITED_FOR_OVERRIDE) { firDiagnostic ->
JsNameProhibitedForOverrideImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED) { firDiagnostic ->
JsNameOnPrimaryConstructorProhibitedImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.JS_NAME_ON_ACCESSOR_AND_PROPERTY) { firDiagnostic ->
JsNameOnAccessorAndPropertyImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY) { firDiagnostic ->
JsNameProhibitedForExtensionPropertyImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.OPT_IN_USAGE) { firDiagnostic ->
OptInUsageImpl(
firDiagnostic.a,
@@ -761,6 +761,30 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = NativeSetterWrongReturnType::class
}
abstract class JsNameIsNotOnAllAccessors : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsNameIsNotOnAllAccessors::class
}
abstract class JsNameProhibitedForNamedNative : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsNameProhibitedForNamedNative::class
}
abstract class JsNameProhibitedForOverride : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsNameProhibitedForOverride::class
}
abstract class JsNameOnPrimaryConstructorProhibited : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsNameOnPrimaryConstructorProhibited::class
}
abstract class JsNameOnAccessorAndProperty : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsNameOnAccessorAndProperty::class
}
abstract class JsNameProhibitedForExtensionProperty : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsNameProhibitedForExtensionProperty::class
}
abstract class OptInUsage : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = OptInUsage::class
abstract val optInMarkerFqName: FqName
@@ -915,6 +915,36 @@ internal class NativeSetterWrongReturnTypeImpl(
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NativeSetterWrongReturnType(), KtAbstractFirDiagnostic<KtDeclaration>
internal class JsNameIsNotOnAllAccessorsImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsNameIsNotOnAllAccessors(), KtAbstractFirDiagnostic<KtElement>
internal class JsNameProhibitedForNamedNativeImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsNameProhibitedForNamedNative(), KtAbstractFirDiagnostic<KtElement>
internal class JsNameProhibitedForOverrideImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsNameProhibitedForOverride(), KtAbstractFirDiagnostic<KtElement>
internal class JsNameOnPrimaryConstructorProhibitedImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsNameOnPrimaryConstructorProhibited(), KtAbstractFirDiagnostic<KtElement>
internal class JsNameOnAccessorAndPropertyImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsNameOnAccessorAndProperty(), KtAbstractFirDiagnostic<KtElement>
internal class JsNameProhibitedForExtensionPropertyImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsNameProhibitedForExtensionProperty(), KtAbstractFirDiagnostic<KtElement>
internal class OptInUsageImpl(
override val optInMarkerFqName: FqName,
override val message: String,
@@ -39,6 +39,12 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
}
val NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE by error<KtDeclaration>(PositioningStrategy.DECLARATION_RETURN_TYPE)
val NATIVE_SETTER_WRONG_RETURN_TYPE by error<KtDeclaration>(PositioningStrategy.DECLARATION_RETURN_TYPE)
val JS_NAME_IS_NOT_ON_ALL_ACCESSORS by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val JS_NAME_PROHIBITED_FOR_NAMED_NATIVE by error<KtElement>()
val JS_NAME_PROHIBITED_FOR_OVERRIDE by error<KtElement>()
val JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED by error<KtElement>()
val JS_NAME_ON_ACCESSOR_AND_PROPERTY by error<KtElement>()
val JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY by error<KtElement>()
}
val SUPERTYPES by object : DiagnosticGroup("Supertypes") {
@@ -39,6 +39,12 @@ object FirJsErrors {
val NATIVE_INDEXER_CAN_NOT_HAVE_DEFAULT_ARGUMENTS by error1<KtElement, String>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE by error0<KtDeclaration>(SourceElementPositioningStrategies.DECLARATION_RETURN_TYPE)
val NATIVE_SETTER_WRONG_RETURN_TYPE by error0<KtDeclaration>(SourceElementPositioningStrategies.DECLARATION_RETURN_TYPE)
val JS_NAME_IS_NOT_ON_ALL_ACCESSORS by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val JS_NAME_PROHIBITED_FOR_NAMED_NATIVE by error0<KtElement>()
val JS_NAME_PROHIBITED_FOR_OVERRIDE by error0<KtElement>()
val JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED by error0<KtElement>()
val JS_NAME_ON_ACCESSOR_AND_PROPERTY by error0<KtElement>()
val JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY by error0<KtElement>()
// Supertypes
val WRONG_MULTIPLE_INHERITANCE by error1<KtElement, FirCallableSymbol<*>>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
@@ -27,6 +27,12 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_EXTER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_VAR
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_JS_MODULE_PROHIBITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_IS_NOT_ON_ALL_ACCESSORS
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_ON_ACCESSOR_AND_PROPERTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_PROHIBITED_FOR_NAMED_NATIVE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_NAME_PROHIBITED_FOR_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NATIVE_ANNOTATIONS_ALLOWED_ONLY_ON_MEMBER_OR_EXTENSION_FUN
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NATIVE_INDEXER_CAN_NOT_HAVE_DEFAULT_ARGUMENTS
@@ -135,6 +141,12 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"Can''t put non-external declarations in file marked with {0} annotation",
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY, "@JsName is prohibited for extension properties")
map.put(JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all of the property accessors")
map.put(JS_NAME_PROHIBITED_FOR_NAMED_NATIVE, "@JsName is prohibited for external declaration with explicit name")
map.put(JS_NAME_PROHIBITED_FOR_OVERRIDE, "@JsName is prohibited for overridden members")
map.put(JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors")
map.put(JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them")
map.checkMissingMessages(FirJsErrors)
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.directOverriddenFunctions
import org.jetbrains.kotlin.fir.analysis.checkers.getAnnotationStringParameter
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.hasAnnotationOrInsideAnnotatedClass
import org.jetbrains.kotlin.fir.declarations.*
@@ -66,6 +67,10 @@ fun FirFunctionSymbol<*>.isOverridingExternalWithOptionalParams(context: Checker
return false
}
fun FirBasedSymbol<*>.getJsName(session: FirSession): String? {
return getAnnotationStringParameter(JsStandardClassIds.Annotations.JsName, session)
}
fun FirBasedSymbol<*>.isNativeObject(session: FirSession): Boolean {
if (hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session) || isEffectivelyExternal(session)) {
return true
@@ -26,6 +26,7 @@ object JsDeclarationCheckers : DeclarationCheckers() {
FirJsRuntimeAnnotationChecker,
FirJsExternalChecker,
FirJsExternalFileChecker,
FirJsNameChecker,
)
override val classCheckers: Set<FirClassChecker>
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2023 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.js.checkers.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.getAnnotationStringParameter
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isExtension
import org.jetbrains.kotlin.name.JsStandardClassIds
import org.jetbrains.kotlin.fir.analysis.js.checkers.getJsName
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
object FirJsNameChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration is FirProperty) {
val accessors = listOfNotNull(declaration.getter?.symbol, declaration.setter?.symbol)
val namedAccessorCount = accessors.count { it.getJsName(context.session) != null }
if (namedAccessorCount > 0 && namedAccessorCount < accessors.size) {
reporter.reportOn(declaration.source, FirJsErrors.JS_NAME_IS_NOT_ON_ALL_ACCESSORS, context)
}
}
val jsName = declaration.getAnnotationByClassId(JsStandardClassIds.Annotations.JsName, context.session) ?: return
val jsNameSource = jsName.source ?: declaration.source
if (declaration.symbol.getAnnotationStringParameter(JsStandardClassIds.Annotations.JsNative, context.session) != null) {
reporter.reportOn(jsNameSource, FirJsErrors.JS_NAME_PROHIBITED_FOR_NAMED_NATIVE, context)
}
if (
declaration is FirCallableDeclaration && declaration.isOverride ||
declaration is FirPropertyAccessor && declaration.propertySymbol.isOverride
) {
reporter.reportOn(jsNameSource, FirJsErrors.JS_NAME_PROHIBITED_FOR_OVERRIDE, context)
}
when (declaration) {
is FirConstructor -> {
if (declaration.isPrimary) {
reporter.reportOn(jsNameSource, FirJsErrors.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, context)
}
}
is FirPropertyAccessor -> {
val property = declaration.propertySymbol
if (property.getJsName(context.session) != null) {
reporter.reportOn(jsNameSource, FirJsErrors.JS_NAME_ON_ACCESSOR_AND_PROPERTY, context)
}
}
is FirProperty -> {
if (declaration.isExtension) {
reporter.reportOn(jsNameSource, FirJsErrors.JS_NAME_PROHIBITED_FOR_EXTENSION_PROPERTY, context)
}
}
else -> {}
}
}
}
@@ -733,3 +733,9 @@ fun FirBasedSymbol<*>.hasAnnotationOrInsideAnnotatedClass(classId: ClassId, sess
fun FirDeclaration.hasAnnotationOrInsideAnnotatedClass(classId: ClassId, session: FirSession) =
symbol.hasAnnotationOrInsideAnnotatedClass(classId, session)
fun FirBasedSymbol<*>.getAnnotationStringParameter(classId: ClassId, session: FirSession): String? {
val annotation = getAnnotationByClassId(classId, session) as? FirAnnotationCall
val expression = annotation?.argumentMapping?.mapping?.values?.firstOrNull() as? FirConstExpression<*>
return expression?.value as? String
}
@@ -1,11 +1,11 @@
package foo
class A {
var x: Int
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var x: Int<!>
@JsName("get_x") get() = 23
set(value) {}
var y: Int
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var y: Int<!>
get() = 23
@JsName("set_y") set(value) {}
@@ -14,10 +14,10 @@ class A {
@JsName("set_z") set(value) {}
}
var xx: Int
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var xx: Int<!>
@JsName("get_xx") get() = 23
set(value) {}
var A.ext: Int
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var A.ext: Int<!>
@JsName("get_ext") get() = 23
set(value) {}
@@ -1,19 +0,0 @@
package foo
open class A {
@JsName("foo_") open fun foo() = 23
@JsName("bar_") open val bar = 123
open val baz: Int
@JsName("getBaz_") get() = 55
}
class B : A() {
@JsName("foo__") override fun foo() = 42
@JsName("bar__") override val bar = 142
override val baz: Int
@JsName("getBaz__") get() = 155
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
open class A {
@@ -2,20 +2,20 @@ package foo
class A {
@JsName("x_") val x: Int
@JsName("get_x") get() = 23
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_x")<!> get() = 23
@JsName("y_") val y = 0
@JsName("m_") var m: Int
@JsName("get_m") get() = 23
@JsName("set_m") set(value) {}
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_m")<!> get() = 23
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("set_m")<!> set(value) {}
}
@JsName("xx_") val xx: Int
@JsName("get_xx") get() = 23
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_xx")<!> get() = 23
@JsName("yy_") val yy = 0
@JsName("mm_") var mm: Int
@JsName("get_mm") get() = 23
@JsName("set_mm") set(value) {}
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_mm")<!> get() = 23
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("set_mm")<!> set(value) {}
@@ -1,3 +0,0 @@
package foo
class A @JsName("B") constructor()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
class A <!JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED!>@JsName("B")<!> constructor()
@@ -1,9 +0,0 @@
package foo
class A
@JsName("xx") val A.x: Int
get() = 23
@property:JsName("yy") val A.y: Int
get() = 42
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
class A
@@ -1,5 +0,0 @@
class A {
@property:JsName("x_") @get:JsName("getX_") val x: Int = 0
@get:JsName("getY_") var y: Int = 0
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class A {
@property:JsName("x_") <!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@get:JsName("getX_")<!> val x: Int = 0
@@ -20,6 +20,7 @@ object JsStandardClassIds {
val JsNativeInvoke = "nativeInvoke".jsId()
val JsNativeGetter = "nativeGetter".jsId()
val JsNativeSetter = "nativeSetter".jsId()
val JsName = "JsName".jsId()
val annotationsRequiringExternal = setOf(JsModule, JsQualifier)