[FIR JS] Report JS_MODULE-related diagnostics

This commit is contained in:
Nikolay Lunyak
2023-01-03 14:27:37 +02:00
committed by Space Team
parent f422a3a4e8
commit 0426f35684
20 changed files with 210 additions and 43 deletions
@@ -982,6 +982,24 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJsErrors.JS_MODULE_PROHIBITED_ON_VAR) { firDiagnostic ->
JsModuleProhibitedOnVarImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE) { firDiagnostic ->
JsModuleProhibitedOnNonNativeImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.NESTED_JS_MODULE_PROHIBITED) { firDiagnostic ->
NestedJsModuleProhibitedImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.OPT_IN_USAGE) { firDiagnostic ->
OptInUsageImpl(
firDiagnostic.a,
@@ -707,6 +707,18 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = WrongJsQualifier::class
}
abstract class JsModuleProhibitedOnVar : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsModuleProhibitedOnVar::class
}
abstract class JsModuleProhibitedOnNonNative : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = JsModuleProhibitedOnNonNative::class
}
abstract class NestedJsModuleProhibited : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = NestedJsModuleProhibited::class
}
abstract class OptInUsage : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = OptInUsage::class
abstract val optInMarkerFqName: FqName
@@ -849,6 +849,21 @@ internal class WrongJsQualifierImpl(
override val token: KtLifetimeToken,
) : KtFirDiagnostic.WrongJsQualifier(), KtAbstractFirDiagnostic<KtElement>
internal class JsModuleProhibitedOnVarImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsModuleProhibitedOnVar(), KtAbstractFirDiagnostic<KtElement>
internal class JsModuleProhibitedOnNonNativeImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.JsModuleProhibitedOnNonNative(), KtAbstractFirDiagnostic<KtElement>
internal class NestedJsModuleProhibitedImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NestedJsModuleProhibited(), KtAbstractFirDiagnostic<KtElement>
internal class OptInUsageImpl(
override val optInMarkerFqName: FqName,
override val message: String,
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.checkers.generator.diagnostics
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.psi.KtElement
@Suppress("UNUSED_VARIABLE", "LocalVariableName", "ClassName", "unused")
@@ -14,5 +15,8 @@ import org.jetbrains.kotlin.psi.KtElement
object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val ANNOTATIONS by object : DiagnosticGroup("Annotations") {
val WRONG_JS_QUALIFIER by error<KtElement>()
val JS_MODULE_PROHIBITED_ON_VAR by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val JS_MODULE_PROHIBITED_ON_NON_NATIVE by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val NESTED_JS_MODULE_PROHIBITED by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.analysis.diagnostics.js
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.psi.KtElement
@@ -18,6 +19,9 @@ import org.jetbrains.kotlin.psi.KtElement
object FirJsErrors {
// Annotations
val WRONG_JS_QUALIFIER by error0<KtElement>()
val JS_MODULE_PROHIBITED_ON_VAR by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val JS_MODULE_PROHIBITED_ON_NON_NATIVE by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NESTED_JS_MODULE_PROHIBITED by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
init {
RootDiagnosticRendererFactory.registerFactory(FirJsErrorsDefaultMessages)
@@ -8,12 +8,21 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics.js
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.checkMissingMessages
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.WRONG_JS_QUALIFIER
@Suppress("unused")
object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
override val MAP = KtDiagnosticFactoryToRendererMap("FIR").also { map ->
map.put(WRONG_JS_QUALIFIER, "Qualifier contains illegal characters")
map.put(JS_MODULE_PROHIBITED_ON_VAR, "@JsModule and @JsNonModule annotations prohibited for 'var' declarations. Use 'val' instead.")
map.put(JS_MODULE_PROHIBITED_ON_NON_NATIVE, "@JsModule and @JsNonModule annotations prohibited for non-external declarations.")
map.put(
NESTED_JS_MODULE_PROHIBITED,
"@JsModule and @JsNonModule can't appear on here since the file is already marked by either @JsModule or @JsNonModule"
)
map.checkMissingMessages(FirJsErrors)
}
@@ -0,0 +1,57 @@
/*
* 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.
*/
@file:OptIn(SymbolInternals::class)
package org.jetbrains.kotlin.fir.analysis.js.checkers
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.hasAnnotationOrInsideAnnotatedClass
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.name.JsStandardClassIds
private val FirBasedSymbol<*>.isExternal
get() = when (this) {
is FirCallableSymbol<*> -> isExternal
is FirClassSymbol<*> -> isExternal
else -> false
}
fun FirBasedSymbol<*>.isEffectivelyExternal(session: FirSession): Boolean {
if (fir is FirMemberDeclaration && isExternal) return true
if (this is FirPropertyAccessorSymbol) {
val property = propertySymbol
if (property.isEffectivelyExternal(session)) return true
}
if (this is FirPropertySymbol) {
if (getterSymbol?.isExternal == true && (!isVar || setterSymbol?.isExternal == true)) {
return true
}
}
return getContainingClassSymbol(session)?.isEffectivelyExternal(session) == true
}
fun FirBasedSymbol<*>.isNativeObject(session: FirSession): Boolean {
if (hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session) || isEffectivelyExternal(session)) {
return true
}
if (this is FirPropertyAccessorSymbol) {
return propertySymbol.hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session)
}
return false
}
fun FirBasedSymbol<*>.isNativeObject(context: CheckerContext) = isNativeObject(context.session)
@@ -6,10 +6,12 @@
package org.jetbrains.kotlin.fir.analysis.js.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirPropertyChecker
import org.jetbrains.kotlin.fir.analysis.js.checkers.declaration.FirJsInlineDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.js.checkers.declaration.FirJsInlinePropertyChecker
import org.jetbrains.kotlin.fir.analysis.js.checkers.declaration.FirJsModuleChecker
object JsDeclarationCheckers : DeclarationCheckers() {
override val functionCheckers: Set<FirFunctionChecker>
@@ -21,4 +23,9 @@ object JsDeclarationCheckers : DeclarationCheckers() {
get() = setOf(
FirJsInlinePropertyChecker,
)
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
get() = setOf(
FirJsModuleChecker,
)
}
@@ -0,0 +1,47 @@
/*
* 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.js.checkers.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.closestNonLocalWith
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.isTopLevel
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.analysis.js.checkers.isNativeObject
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsModule
import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsNonModule
import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
object FirJsModuleChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration is FirFile || !declaration.isEitherModuleOrNonModule(context.session)) return
if (declaration is FirProperty && declaration.isVar) {
reporter.reportOn(declaration.source, FirJsErrors.JS_MODULE_PROHIBITED_ON_VAR, context)
}
val closestNonLocal = context.closestNonLocalWith(declaration)?.symbol ?: return
if (!closestNonLocal.isNativeObject(context)) {
reporter.reportOn(declaration.source, FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE, context)
}
if (context.isTopLevel) {
val file = context.containingDeclarations.lastIsInstanceOrNull<FirFile>()
if (file != null && file.isEitherModuleOrNonModule(context.session)) {
reporter.reportOn(declaration.source, FirJsErrors.NESTED_JS_MODULE_PROHIBITED, context)
}
}
}
private fun FirDeclaration.isEitherModuleOrNonModule(session: FirSession) =
hasAnnotation(JsModule, session) || hasAnnotation(JsNonModule, session)
}
@@ -707,3 +707,14 @@ fun ConeKotlinType.getInlineClassUnderlyingType(session: FirSession): ConeKotlin
require(this.isSingleFieldValueClass(session))
return toRegularClassSymbol(session)!!.primaryConstructorSymbol()!!.valueParameterSymbols[0].resolvedReturnTypeRef.coneType
}
fun CheckerContext.closestNonLocalWith(declaration: FirDeclaration) =
(containingDeclarations + declaration).takeWhile { it.isNonLocal }.lastOrNull()
val CheckerContext.isTopLevel get() = containingDeclarations.lastOrNull() is FirFile
fun FirBasedSymbol<*>.hasAnnotationOrInsideAnnotatedClass(classId: ClassId, session: FirSession): Boolean {
if (hasAnnotation(classId, session)) return true
val container = getContainingClassSymbol(session) ?: return false
return container.hasAnnotationOrInsideAnnotatedClass(classId, session)
}
@@ -33,3 +33,13 @@ inline val FirDeclaration.isPrecompiled: Boolean
get() = origin == FirDeclarationOrigin.Precompiled
inline val FirDeclaration.isSynthetic: Boolean
get() = origin == FirDeclarationOrigin.Synthetic
// NB: This function checks transitive localness. That is,
// if a declaration `isNonLocal`, then its parent also `isNonLocal`.
val FirDeclaration.isNonLocal
get() = when (this) {
is FirFile -> true
is FirCallableDeclaration -> !symbol.callableId.isLocal
is FirClassLikeDeclaration -> !symbol.classId.isLocal
else -> false
}
@@ -104,6 +104,7 @@ class FirAnonymousFunctionSymbol : FirFunctionWithoutNameSymbol<FirAnonymousFunc
class FirPropertyAccessorSymbol : FirFunctionWithoutNameSymbol<FirPropertyAccessor>(Name.identifier("accessor")) {
val isGetter: Boolean get() = fir.isGetter
val isSetter: Boolean get() = fir.isSetter
val propertySymbol get() = fir.propertySymbol
}
class FirErrorFunctionSymbol : FirFunctionWithoutNameSymbol<FirErrorFunction>(Name.identifier("error"))
@@ -1,7 +0,0 @@
package foo
@JsModule("bar")
external var bar: Int = definedExternally
@JsNonModule
external var baz: Int = definedExternally
@@ -1,7 +1,13 @@
// FIR_IDENTICAL
package foo
<!JS_MODULE_PROHIBITED_ON_VAR!>@JsModule("bar")
external var bar: Int<!> = definedExternally
<!JS_MODULE_PROHIBITED_ON_VAR!>@JsNonModule
external var baz: Int<!> = definedExternally
external var baz: Int<!> = definedExternally
typealias JsM = JsModule
<!JS_MODULE_PROHIBITED_ON_VAR!>@JsM("bar")
external var bar2: Int<!> = definedExternally
@@ -2,5 +2,7 @@ package
package foo {
@kotlin.js.JsModule(import = "bar") public external var bar: kotlin.Int
@foo.JsM /* = kotlin.js.JsModule */(import = "bar") public external var bar2: kotlin.Int
@kotlin.js.JsNonModule public external var baz: kotlin.Int
public typealias JsM = kotlin.js.JsModule
}
@@ -1,19 +0,0 @@
@file:JsModule("foo")
package foo
@JsModule("A")
external class A {
class Nested
}
@JsModule("B")
external object B
@JsModule("foo")
external fun foo(): Int
@JsModule("bar")
external val bar: Int
@JsNonModule
external val baz: Int
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
@file:JsModule("foo")
package foo
@@ -1,16 +0,0 @@
package foo
@JsModule("A")
class A
@JsModule("B")
object B
@JsModule("foo")
fun foo() = 23
@JsModule("bar")
val bar = 42
@JsNonModule
val baz = 99
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package foo
@JsModule("A")
@@ -12,6 +12,10 @@ object JsStandardClassIds {
object Annotations {
val JsQualifier = "JsQualifier".jsId()
val JsModule = "JsModule".jsId()
val JsNonModule = "JsNonModule".jsId()
val JsNative = "native".jsId()
}
}