diff --git a/.space/CODEOWNERS b/.space/CODEOWNERS index a0da16dbfa8..df481535ecf 100644 --- a/.space/CODEOWNERS +++ b/.space/CODEOWNERS @@ -206,6 +206,7 @@ /core/builtins/ "Kotlin Compiler Core" /core/compiler.common/ "Kotlin Compiler Core" /core/compiler.common.jvm/ "Kotlin Compiler Core" +/core/compiler.common.js/ "Kotlin Compiler Core" /core/descriptors/ "Kotlin Compiler Core" /core/descriptors.jvm/ "Kotlin Compiler Core" /core/descriptors.runtime/ "Kotlin Libraries" diff --git a/analysis/analysis-api-fir/analysis-api-fir-generator/src/org/jetbrains/kotlin/analysis/api/fir/generator/Main.kt b/analysis/analysis-api-fir/analysis-api-fir-generator/src/org/jetbrains/kotlin/analysis/api/fir/generator/Main.kt index 25449f2c256..9c60ffa04a3 100644 --- a/analysis/analysis-api-fir/analysis-api-fir-generator/src/org/jetbrains/kotlin/analysis/api/fir/generator/Main.kt +++ b/analysis/analysis-api-fir/analysis-api-fir-generator/src/org/jetbrains/kotlin/analysis/api/fir/generator/Main.kt @@ -10,10 +10,11 @@ import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.JVM_DIAGNOSTICS_L import java.nio.file.Paths import org.jetbrains.kotlin.analysis.api.fir.generator.DiagnosticClassGenerator.generate import org.jetbrains.kotlin.fir.builder.SYNTAX_DIAGNOSTIC_LIST +import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.JS_DIAGNOSTICS_LIST fun main() { val rootPath = Paths.get("analysis/analysis-api-fir/src").toAbsolutePath() val packageName = "org.jetbrains.kotlin.analysis.api.fir.diagnostics" - val diagnostics = DIAGNOSTICS_LIST + JVM_DIAGNOSTICS_LIST + SYNTAX_DIAGNOSTIC_LIST + val diagnostics = DIAGNOSTICS_LIST + JVM_DIAGNOSTICS_LIST + JS_DIAGNOSTICS_LIST + SYNTAX_DIAGNOSTIC_LIST generate(rootPath, diagnostics, packageName) } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 6207e04a9bc..97f593dbad7 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -976,6 +976,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirJsErrors.WRONG_JS_QUALIFIER) { firDiagnostic -> + WrongJsQualifierImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.OPT_IN_USAGE) { firDiagnostic -> OptInUsageImpl( firDiagnostic.a, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index edfc32d3692..bf4b2c82be6 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -703,6 +703,10 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val typeFromTypesPhase: KtType } + abstract class WrongJsQualifier : KtFirDiagnostic() { + override val diagnosticClass get() = WrongJsQualifier::class + } + abstract class OptInUsage : KtFirDiagnostic() { override val diagnosticClass get() = OptInUsage::class abstract val optInMarkerFqName: FqName diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index ec89a7e5feb..d14e398511c 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -844,6 +844,11 @@ internal class PluginAnnotationAmbiguityImpl( override val token: KtLifetimeToken, ) : KtFirDiagnostic.PluginAnnotationAmbiguity(), KtAbstractFirDiagnostic +internal class WrongJsQualifierImpl( + override val firDiagnostic: KtPsiDiagnostic, + override val token: KtLifetimeToken, +) : KtFirDiagnostic.WrongJsQualifier(), KtAbstractFirDiagnostic + internal class OptInUsageImpl( override val optInMarkerFqName: FqName, override val message: String, diff --git a/analysis/analysis-api/build.gradle.kts b/analysis/analysis-api/build.gradle.kts index a8fa367ac52..f9ba36ba9e1 100644 --- a/analysis/analysis-api/build.gradle.kts +++ b/analysis/analysis-api/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { compileOnly(project(":compiler:psi")) compileOnly(project(":core:compiler.common")) compileOnly(project(":core:compiler.common.jvm")) + compileOnly(project(":core:compiler.common.js")) implementation(project(":analysis:analysis-internal-utils")) implementation(project(":analysis:analysis-api-providers")) implementation(project(":analysis:kt-references")) diff --git a/build.gradle.kts b/build.gradle.kts index f0d52d24ed7..3b744c05959 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -142,6 +142,7 @@ val commonCompilerModules = arrayOf( ":core:deserialization.common.jvm", ":core:compiler.common", ":core:compiler.common.jvm", + ":core:compiler.common.js", ":core:util.runtime", ":compiler:frontend.common.jvm", ":compiler:frontend.java", // TODO this is fe10 module but some utils used in fir ide now @@ -237,6 +238,7 @@ extra["kotlinJpsPluginEmbeddedDependencies"] = listOf( ":daemon-common", ":core:compiler.common", ":core:compiler.common.jvm", + ":core:compiler.common.js", ":core:descriptors", ":core:descriptors.jvm", ":compiler:backend.common.jvm", diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt index 96fd05a648c..6bb656378e2 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJsDiagnosticsList.kt @@ -7,7 +7,12 @@ 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.psi.KtElement @Suppress("UNUSED_VARIABLE", "LocalVariableName", "ClassName", "unused") @OptIn(PrivateForInline::class) -object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") +object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") { + val ANNOTATIONS by object : DiagnosticGroup("Annotations") { + val WRONG_JS_QUALIFIER by error() + } +} diff --git a/compiler/fir/checkers/checkers.js/build.gradle.kts b/compiler/fir/checkers/checkers.js/build.gradle.kts index 88fdbdb5b2e..7491e5b5af9 100644 --- a/compiler/fir/checkers/checkers.js/build.gradle.kts +++ b/compiler/fir/checkers/checkers.js/build.gradle.kts @@ -6,6 +6,7 @@ plugins { } dependencies { + api(project(":core:compiler.common.js")) api(project(":compiler:fir:checkers")) /* diff --git a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt index 652801a6614..f88003836a2 100644 --- a/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt +++ b/compiler/fir/checkers/checkers.js/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrors.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics.js import org.jetbrains.kotlin.diagnostics.* import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory import org.jetbrains.kotlin.fir.analysis.diagnostics.* +import org.jetbrains.kotlin.psi.KtElement /* * This file was generated automatically @@ -15,6 +16,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.* */ object FirJsErrors { + // Annotations + val WRONG_JS_QUALIFIER by error0() + init { RootDiagnosticRendererFactory.registerFactory(FirJsErrorsDefaultMessages) } diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt index 8312eca589b..478cd8facd0 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/diagnostics/js/FirJsErrorsDefaultMessages.kt @@ -7,9 +7,14 @@ 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.WRONG_JS_QUALIFIER @Suppress("unused") object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() { - override val MAP = KtDiagnosticFactoryToRendererMap("FIR").also { _ -> + override val MAP = KtDiagnosticFactoryToRendererMap("FIR").also { map -> + map.put(WRONG_JS_QUALIFIER, "Qualifier contains illegal characters") + + map.checkMissingMessages(FirJsErrors) } } \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt index 47bd9bd91c9..c4002263d2b 100644 --- a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/JsExpressionCheckers.kt @@ -6,5 +6,11 @@ package org.jetbrains.kotlin.fir.analysis.js.checkers import org.jetbrains.kotlin.fir.analysis.checkers.expression.* +import org.jetbrains.kotlin.fir.analysis.js.checkers.expression.FirJsQualifierChecker -object JsExpressionCheckers : ExpressionCheckers() +object JsExpressionCheckers : ExpressionCheckers() { + override val annotationCallCheckers: Set + get() = setOf( + FirJsQualifierChecker, + ) +} diff --git a/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/expression/FirJsQualifierChecker.kt b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/expression/FirJsQualifierChecker.kt new file mode 100644 index 00000000000..0be8be7e68e --- /dev/null +++ b/compiler/fir/checkers/checkers.js/src/org/jetbrains/kotlin/fir/analysis/js/checkers/expression/FirJsQualifierChecker.kt @@ -0,0 +1,33 @@ +/* + * 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.expression + +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.expression.FirAnnotationCallChecker +import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors +import org.jetbrains.kotlin.fir.declarations.fullyExpandedClassId +import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall +import org.jetbrains.kotlin.fir.expressions.FirConstExpression +import org.jetbrains.kotlin.js.validateQualifier +import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsQualifier + +object FirJsQualifierChecker : FirAnnotationCallChecker() { + override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) { + val annotationFqName = expression.fullyExpandedClassId(context.session) + + if (annotationFqName != JsQualifier) { + return + } + + val string = (expression.argumentMapping.mapping.values.firstOrNull() as? FirConstExpression<*>)?.value as? String ?: return + + if (!validateQualifier(string)) { + reporter.reportOn(expression.argumentList.arguments.first().source, FirJsErrors.WRONG_JS_QUALIFIER, context) + } + } +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.fir.kt b/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.fir.kt deleted file mode 100644 index 8e1a002a6ba..00000000000 --- a/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.fir.kt +++ /dev/null @@ -1,17 +0,0 @@ -// FILE: a.kt -@file:JsQualifier("") - -// FILE: b.kt -@file:JsQualifier("a..b") - -// FILE: c.kt -@file:JsQualifier("a.") - -// FILE: d.kt -@file:JsQualifier(".a") - -// FILE: e.kt -@file:JsQualifier("%^&") - -// FILE: f.kt -@file:JsQualifier("a.bc.d23._$") diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.kt b/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.kt index a45793e2ac5..a188aef0d30 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.kt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // FILE: a.kt @file:JsQualifier("") @@ -14,4 +15,10 @@ @file:JsQualifier("%^&") // FILE: f.kt -@file:JsQualifier("a.bc.d23._$") \ No newline at end of file +@file:JsQualifier("a.bc.d23._$") + +// FILE: g.kt +typealias JsQ = JsQualifier + +// FILE: h.kt +@file:JsQ(value = "%^&") diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.txt b/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.txt index ba3bd787383..601ba4ce26b 100644 --- a/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.txt +++ b/compiler/testData/diagnostics/testsWithJsStdLib/qualifier/wrongQualifier.txt @@ -1 +1,4 @@ package + +public typealias JsQ = kotlin.js.JsQualifier + diff --git a/core/compiler.common.js/build.gradle.kts b/core/compiler.common.js/build.gradle.kts new file mode 100644 index 00000000000..636b5c3c31b --- /dev/null +++ b/core/compiler.common.js/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +project.configureJvmToolchain(JdkMajorVersion.JDK_1_8) + +dependencies { + api(project(":core:compiler.common")) +} + +sourceSets { + "main" { projectDefault() } + "test" {} +} diff --git a/core/compiler.common.js/src/org/jetbrains/kotlin/js/JsHelpers.kt b/core/compiler.common.js/src/org/jetbrains/kotlin/js/JsHelpers.kt new file mode 100644 index 00000000000..a81acf33489 --- /dev/null +++ b/core/compiler.common.js/src/org/jetbrains/kotlin/js/JsHelpers.kt @@ -0,0 +1,15 @@ +/* + * 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.js + +fun validateQualifier(qualifier: String): Boolean { + val parts = qualifier.split('.') + if (parts.isEmpty()) return false + + return parts.all { part -> + part.isNotEmpty() && part[0].isJavaIdentifierStart() && part.drop(1).all(Char::isJavaIdentifierPart) + } +} diff --git a/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt b/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt new file mode 100644 index 00000000000..7ed1d2b27a8 --- /dev/null +++ b/core/compiler.common.js/src/org/jetbrains/kotlin/name/JsStandardClassIds.kt @@ -0,0 +1,18 @@ +/* + * 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.name + +import org.jetbrains.kotlin.name.StandardClassIds.BASE_KOTLIN_PACKAGE + +object JsStandardClassIds { + val BASE_JS_PACKAGE = BASE_KOTLIN_PACKAGE.child(Name.identifier("js")) + + object Annotations { + val JsQualifier = "JsQualifier".jsId() + } +} + +private fun String.jsId() = ClassId(JsStandardClassIds.BASE_JS_PACKAGE, Name.identifier(this)) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsQualifierChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsQualifierChecker.kt index 56ba44a9c88..feb2cc238a7 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsQualifierChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsQualifierChecker.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.resolve.diagnostics import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.js.validateQualifier import org.jetbrains.kotlin.psi.KtAnnotated import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker @@ -44,13 +45,4 @@ object JsQualifierChecker : AdditionalAnnotationChecker { } } } - - private fun validateQualifier(qualifier: String): Boolean { - val parts = qualifier.split('.') - if (parts.isEmpty()) return false - - return parts.all { part -> - part.isNotEmpty() && part[0].isJavaIdentifierStart() && part.drop(1).all(Char::isJavaIdentifierPart) - } - } } diff --git a/settings.gradle b/settings.gradle index 4b521c75e83..c048420b80b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -152,6 +152,7 @@ include ":benchmarks", ":native:kotlin-klib-commonizer-embeddable", ":core:compiler.common", ":core:compiler.common.jvm", + ":core:compiler.common.js", ":compiler:backend.common.jvm", ":core:descriptors", ":core:descriptors.jvm",