[build] add checks to ensure that no modules which are part of the IDE plugin do not use experimental stdlib API

to ensure binary compatibility with stdlib inside IntelliJ.

This includes using the latest stable kotlin API version and
forbidding using experimental declarations from stdlib.

^KT-62510
This commit is contained in:
Ilya Kirillov
2023-10-11 19:27:29 +02:00
committed by Space Team
parent ded5cf2caa
commit a65c735feb
18 changed files with 670 additions and 1 deletions
+1
View File
@@ -286,6 +286,7 @@
/libraries/tools/atomicfu/ "Kotlin Libraries" /libraries/tools/atomicfu/ "Kotlin Libraries"
/libraries/tools/binary-compatibility-validator/ "Kotlin Libraries" /libraries/tools/binary-compatibility-validator/ "Kotlin Libraries"
/libraries/tools/dukat/ "Kotlin JS" /libraries/tools/dukat/ "Kotlin JS"
/libraries/tools/ide-plugin-dependencies-validator "Kotlin IDE Analysis Core"
/libraries/kotlin-dom-api-compat/ "Kotlin JS" /libraries/kotlin-dom-api-compat/ "Kotlin JS"
/libraries/tools/kotlin-build-tools-enum-compat/ "Kotlin Build Tools" /libraries/tools/kotlin-build-tools-enum-compat/ "Kotlin Build Tools"
/libraries/tools/gradle/ "Kotlin Build Tools" /libraries/tools/gradle/ "Kotlin Build Tools"
+96
View File
@@ -1,6 +1,8 @@
import org.gradle.crypto.checksum.Checksum import org.gradle.crypto.checksum.Checksum
import org.gradle.plugins.ide.idea.model.IdeaModel import org.gradle.plugins.ide.idea.model.IdeaModel
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
buildscript { buildscript {
// a workaround for kotlin compiler classpath in kotlin project: sometimes gradle substitutes // a workaround for kotlin compiler classpath in kotlin project: sometimes gradle substitutes
@@ -68,6 +70,7 @@ val kotlinVersion by extra(
) )
val kotlinLanguageVersion: String by extra val kotlinLanguageVersion: String by extra
val kotlinApiVersionForModulesUsedInIDE: String by extra
extra["kotlin_root"] = rootDir extra["kotlin_root"] = rootDir
@@ -245,6 +248,98 @@ extra["compilerModules"] =
commonCompilerModules + commonCompilerModules +
firAllCompilerModules firAllCompilerModules
/**
* An array of projects used in the IntelliJ Kotlin Plugin.
*
* Experimental declarations from Kotlin stdlib cannot be used in those projects to avoid stdlib binary compatibility problems.
* See KT-62510 for details.
*/
val projectsUsedInIntelliJKotlinPlugin =
fe10CompilerModules +
commonCompilerModules +
firCompilerCoreModules +
irCompilerModulesForIDE +
arrayOf(
":analysis:analysis-api",
":analysis:analysis-api-fe10",
":analysis:analysis-api-fir",
":analysis:analysis-api-impl-barebone",
":analysis:analysis-api-impl-base",
":analysis:analysis-api-providers",
":analysis:analysis-api-standalone:analysis-api-standalone-base",
":analysis:analysis-api-standalone:analysis-api-fir-standalone-base",
":analysis:analysis-api-standalone",
":analysis:analysis-internal-utils",
":analysis:analysis-test-framework",
":analysis:decompiled",
":analysis:kt-references",
":analysis:kt-references:kt-references-fe10",
":analysis:light-classes-base",
":analysis:low-level-api-fir",
":analysis:project-structure",
":analysis:symbol-light-classes",
) +
arrayOf(
":kotlin-allopen-compiler-plugin.cli",
":kotlin-allopen-compiler-plugin.common",
":kotlin-allopen-compiler-plugin.k1",
":kotlin-allopen-compiler-plugin.k2",
":kotlin-assignment-compiler-plugin.cli",
":kotlin-assignment-compiler-plugin.common",
":kotlin-assignment-compiler-plugin.k1",
":kotlin-assignment-compiler-plugin.k2",
":plugins:parcelize:parcelize-compiler:parcelize.backend",
":plugins:parcelize:parcelize-compiler:parcelize.cli",
":plugins:parcelize:parcelize-compiler:parcelize.common",
":plugins:parcelize:parcelize-compiler:parcelize.k1",
":plugins:parcelize:parcelize-compiler:parcelize.k2",
":plugins:parcelize:parcelize-runtime",
":kotlin-sam-with-receiver-compiler-plugin.cli",
":kotlin-sam-with-receiver-compiler-plugin.common",
":kotlin-sam-with-receiver-compiler-plugin.k1",
":kotlin-sam-with-receiver-compiler-plugin.k2",
":kotlinx-serialization-compiler-plugin.cli",
":kotlinx-serialization-compiler-plugin.common",
":kotlinx-serialization-compiler-plugin.k1",
":kotlinx-serialization-compiler-plugin.k2",
":kotlinx-serialization-compiler-plugin.backend",
":kotlin-lombok-compiler-plugin.cli",
":kotlin-lombok-compiler-plugin.common",
":kotlin-lombok-compiler-plugin.k1",
":kotlin-lombok-compiler-plugin.k2",
":kotlin-noarg-compiler-plugin.cli",
":kotlin-noarg-compiler-plugin.common",
":kotlin-noarg-compiler-plugin.k1",
":kotlin-noarg-compiler-plugin.k2",
":kotlin-noarg-compiler-plugin.backend",
":plugins:android-extensions-compiler",
":kotlin-sam-with-receiver-compiler-plugin.cli",
":kotlin-sam-with-receiver-compiler-plugin.common",
":kotlin-sam-with-receiver-compiler-plugin.k1",
":kotlin-sam-with-receiver-compiler-plugin.k2",
":kotlin-compiler-runner-unshaded",
":kotlin-preloader",
":daemon-common",
":kotlin-daemon-client",
":kotlin-scripting-compiler",
":kotlin-gradle-statistics",
":jps:jps-common",
) +
if (kotlinBuildProperties.isKotlinNativeEnabled) arrayOf(":kotlin-native:backend.native") else emptyArray()
extra["projectsUsedInIntelliJKotlinPlugin"] = projectsUsedInIntelliJKotlinPlugin
// They are embedded just because we don't publish those dependencies as separate Maven artifacts (yet) // They are embedded just because we don't publish those dependencies as separate Maven artifacts (yet)
extra["kotlinJpsPluginEmbeddedDependencies"] = listOf( extra["kotlinJpsPluginEmbeddedDependencies"] = listOf(
":compiler:cli-common", ":compiler:cli-common",
@@ -786,6 +881,7 @@ tasks {
dependsOn(":kotlin-tooling-core:check") dependsOn(":kotlin-tooling-core:check")
dependsOn(":kotlin-tooling-metadata:check") dependsOn(":kotlin-tooling-metadata:check")
dependsOn(":compiler:build-tools:kotlin-build-tools-api:check") dependsOn(":compiler:build-tools:kotlin-build-tools-api:check")
dependsOn(":tools:ide-plugin-dependencies-validator:test")
} }
register("examplesTest") { register("examplesTest") {
+4
View File
@@ -29,6 +29,10 @@ cacheRedirectorEnabled=true
defaultSnapshotVersion=2.0.255-SNAPSHOT defaultSnapshotVersion=2.0.255-SNAPSHOT
kotlinLanguageVersion=2.0 kotlinLanguageVersion=2.0
# Should be less or equal to the Kotlin stdlib version used inside IntelliJ IDEA repository, see KT-62510.
# IntelliJ IDEA Kotlin stdlib version can be found at https://github.com/JetBrains/intellij-community/blob/master/.idea/libraries/kotlin_stdlib.xml
kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin=1.9
kotlin.build.gradlePlugin.version=0.0.40 kotlin.build.gradlePlugin.version=0.0.40
#maven.repository.mirror=http://repository.jetbrains.com/remote-repos/ #maven.repository.mirror=http://repository.jetbrains.com/remote-repos/
@@ -0,0 +1,10 @@
# The list of experimental Kotlin stdlib annotations.
# Declarations from kotlin stdlib marked with those annotations cannot be used in the projects
# that are published for the IJ Kotlin plugin to avoid stdlib binary compatibility problems.
# See KT-62510 for details.
# The list should be updated as new experimental annotations are added or removed from the stdlib.
kotlin.ExperimentalStdlibApi
kotlin.io.path.ExperimentalPathApi
kotlin.io.encoding.ExperimentalEncodingApi
kotlin.time.ExperimentalTime
kotlin.ExperimentalUnsignedTypes
@@ -0,0 +1,42 @@
# IDE Plugin Dependencies Validator
Some projects inside the Kotlin repository are used inside the IntelliJ Kotlin plugin. Those projects have special restrictions forbidding
experimental Kotlin stdlib API use in them. The `:tools:ide-plugin-dependencies-validator` project is a tool to check that those
restrictions are not violated.
See [KTIJ-20529](https://youtrack.jetbrains.com/issue/KTIJ-20529).
## Details
IntelliJ IDEA has its own bundled Kotlin stdlib, which is used across the IntelliJ repository. This stdlib is usually the latest available
stable Kotlin stdlib. Kotlin repository is compiled against a snapshot Kotlin stdlib. So, the projects from the Kotlin repository,
which bundle to IntelliJ, are compiled against snapshot stdlib, but on the runtime in IntelliJ, there is a fixed and stable version of
Kotlin stdlib. To avoid binary compatibility problems that may arise at runtime in IntelliJ, experimental stdlib declarations for which
binary compatibility is not guaranteed are not allowed to be used inside Kotlin projects used in IntelliJ.
The list of such projects used inside IntelliJ Kotlin Plugin can be found at `projectsUsedInIntelliJKotlinPlugin` property
inside [the root build.gradle.kts](build.gradle.kts)
## Checks
The tool checks on all projects defined in `projectsUsedInIntelliJKotlinPlugin`.
* No opt-ins for experimental stdlib annotations are used inside the source code. The check works by syntax only, and no code resolution is
performed. This is not a hundred percent reliable, but it usually works unless there is a name conflict with experimental opt-in
annotations
from stdlib. The code for those checks can be found at `org.jetbrains.Kotlin.ide.plugin.dependencies.validator` package.
* The Kotlin API version used is the same as the version of Kotlin stdlib used inside the IntelliJ Platform. This is defined
by `kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin` property.
* No opt-ins for experimental stdlib annotations are used inside the build definition files. The check works by
checking that no `-opt-in` arguments with experimental stdlib annotations are defined
in `KotlinJvmCompile.KotlinOptions.freeCompilerArgs`.
## Running Checks
`gradle :tools:ide-plugin-dependencies-validator:checkIdeDependenciesConfiguration` task runs the check. It consists of two subtasks:
* `gradle :tools:ide-plugin-dependencies-validator:checkIdeDependenciesConfiguration` task checks the build configuration of projects.
* `gradle :tools:ide-plugin-dependencies-validator:run` task checks Kotlin source files inside the project for experimental annotations
usages.
@@ -0,0 +1,149 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import java.nio.file.Paths
import kotlin.io.path.readLines
plugins {
application
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(project(":compiler:psi"))
implementation(project(":compiler:cli"))
implementation(intellijCore())
implementation(kotlinStdlib())
// runtime dependencies for IJ
runtimeOnly(commonDependency("org.jetbrains.intellij.deps.fastutil:intellij-deps-fastutil"))
runtimeOnly(commonDependency("org.codehaus.woodstox:stax2-api"))
runtimeOnly(commonDependency("com.fasterxml:aalto-xml"))
runtimeOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
// test dependencies
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
}
projectTest(jUnitMode = JUnitMode.JUnit5) {
workingDir = rootDir
useJUnitPlatform()
}
application {
mainClass.set("org.jetbrains.kotlin.ide.plugin.dependencies.validator.MainKt")
}
val projectsUsedInIntelliJKotlinPlugin: Array<String> by rootProject.extra
val kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin: String by rootProject.extra
tasks.withType<JavaExec> {
workingDir = rootProject.projectDir
doFirst {
args = projectsUsedInIntelliJKotlinPlugin.flatMap {
project(it).extensions
.findByType(JavaPluginExtension::class.java)
?.sourceSets?.flatMap { sourceSet ->
sourceSet.allSource.srcDirs.map { it.path }
}.orEmpty()
}
}
}
tasks.register("checkIdeDependenciesConfiguration") {
doFirst {
for (projectName in projectsUsedInIntelliJKotlinPlugin) {
project(projectName).checkIdeDependencyConfiguration()
}
}
}
fun Project.checkIdeDependencyConfiguration() {
val expectedApiVersion = KotlinVersion.fromVersion(kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin)
for (compileTask in tasks.withType<KotlinJvmCompile>()) {
val projectApiVersion = compileTask.compilerOptions.apiVersion.get()
check(projectApiVersion <= expectedApiVersion) {
"Expected the API Version to be less or equal to `$kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin`" +
" for the project `$name`, " +
"but `$projectApiVersion` found. The project is used in the IntelliJ, so it should use the same API version" +
"for binary compatibility with Kotlin stdlib . " +
"See KT-62510 for details."
}
val enabledExperimentalAnnotations =
ExperimentalAnnotationsCollector().getUsedExperimentalAnnotations(compileTask.kotlinOptions.freeCompilerArgs)
check(enabledExperimentalAnnotations.isEmpty()) {
"`$name` allows using experimental kotlin stdlib API marked with ${enabledExperimentalAnnotations.joinToString()}. " +
"The project is used in the IntelliJ Kotlin Plugin, so it cannot use experimental Kotlin stdlib API " +
"for binary compatibility with Kotlin stdlib . " +
"See KT-62510 for details."
}
}
}
tasks.register("checkIdeDependencies") {
dependsOn("checkIdeDependenciesConfiguration")
dependsOn("run")
}
val validatorProject: Project get() = project
private class ExperimentalAnnotationsCollector() {
val experimentalAnnotations: Set<String> by lazy {
validatorProject.projectDir.toPath().resolve(EXPERIMENTAL_ANNOTATIONS_FILE)
.readLines()
.map { it.trim() }
.filterNot { it.startsWith("#") || it.isBlank() }
.toSet()
}
fun getUsedExperimentalAnnotations(arguments: List<String>): List<String> {
return buildList {
addAll(getOptInAnnotationsByMultipleArguments(arguments))
arguments.flatMapTo(this) { getOptInAnnotationsBySingleArgument(it) }
removeAll { it !in experimentalAnnotations }
}
}
/**
* Returns a list of experimental annotation used in an argument list of kind `["-opt-in", "kotlin.ExperimentalStdlibApi,kotlin.time.ExperimentalTime"]`
*/
private fun getOptInAnnotationsByMultipleArguments(arguments: List<String>): List<String> {
return arguments.windowed(2).flatMap { (argumentName, value) ->
if (argumentName == "-Xopt-in" || argumentName == "-opt-in") value.split(",").map { it.trim() }
else emptyList()
}
}
private fun getOptInAnnotationsBySingleArgument(argument: String): List<String> {
@Suppress("NAME_SHADOWING")
var argument = argument.trim()
argument = when {
argument.startsWith("-opt-in=") -> {
argument.removePrefix("-opt-in=")
}
argument.startsWith("-Xopt-in=") -> {
argument.removePrefix("-Xopt-in=")
}
else -> {
return emptyList()
}
}
return argument.split(",").map { it.trim() }
}
companion object {
private const val EXPERIMENTAL_ANNOTATIONS_FILE = "ExperimentalAnnotations.txt"
}
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
@@ -0,0 +1,35 @@
/*
* 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.ide.plugin.dependencies.validator
import java.nio.file.Path
data class ExperimentalAnnotationUsage(
val file: Path,
val lineNumber: Int,
val usedExperimentalAnnotation: String,
) {
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + file.toAbsolutePath().hashCode()
result = 31 * result + lineNumber
result = 31 * result + usedExperimentalAnnotation.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ExperimentalAnnotationUsage
if (file.toAbsolutePath() != other.file.toAbsolutePath()) return false
if (lineNumber != other.lineNumber) return false
if (usedExperimentalAnnotation != other.usedExperimentalAnnotation) return false
return true
}
}
@@ -0,0 +1,27 @@
/*
* 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.ide.plugin.dependencies.validator
import java.nio.file.Paths
import kotlin.io.path.readLines
object ExperimentalAnnotations {
val experimentalAnnotations: Set<String> by lazy {
Paths.get(EXPERIMENTAL_ANNOTATIONS_PATH)
.readLines()
.map { it.trim() }
.filterNot { it.startsWith("#") || it.isBlank() }
.toSet()
}
val experimentalAnnotationShortNames: Set<String> by lazy {
experimentalAnnotations.mapTo(mutableSetOf()) { it.substringAfterLast('.') }
}
private const val EXPERIMENTAL_ANNOTATIONS_PATH =
"libraries/tools/ide-plugin-dependencies-validator/ExperimentalAnnotations.txt"
}
@@ -0,0 +1,93 @@
/*
* 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.ide.plugin.dependencies.validator
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.impl.PsiFileFactoryImpl
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.streams.asSequence
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.idea.KotlinLanguage
import kotlin.io.path.name
import kotlin.io.path.readText
object ExperimentalOptInUsageInSourceChecker {
fun checkExperimentalOptInUsage(srcRoots: List<Path>): List<ExperimentalAnnotationUsage> {
val project = createProjectForParsing()
try {
return srcRoots.filter { it.exists() }
.flatMap { srcRoot ->
Files.walk(srcRoot)
.asSequence()
.filter { it.extension == "kt" }
.flatMap { file ->
val ktFile = file.parseAsKtFile(project)
checkExperimentalOptInUsage(ktFile, file)
}
}
} finally {
Disposer.dispose(project)
}
}
private fun checkExperimentalOptInUsage(ktFile: KtFile, file: Path): List<ExperimentalAnnotationUsage> {
return ktFile
.collectDescendantsOfType<KtAnnotationEntry>()
.flatMap { annotationEntry ->
val annotationShortName = annotationEntry.shortName?.asString() ?: return@flatMap emptyList()
val experimentalAnnotations = when (annotationShortName) {
OPT_IN_ANNOTATION -> {
annotationEntry.valueArguments.mapNotNull mapArguments@{ argument ->
if (argument !is KtValueArgument) return@mapArguments null
val expression = argument.getArgumentExpression() as? KtClassLiteralExpression ?: return@mapArguments null
val classReference = expression.receiverExpression as? KtNameReferenceExpression ?: return@mapArguments null
classReference.getReferencedName().takeIf { it in ExperimentalAnnotations.experimentalAnnotationShortNames }
}
}
in ExperimentalAnnotations.experimentalAnnotationShortNames -> {
listOf(annotationShortName)
}
else -> return@flatMap emptyList()
}
if (experimentalAnnotations.isEmpty()) return@flatMap emptyList()
/* offsetToLineNumber's indexing starts from 0*/
val lineNumber = StringUtil.offsetToLineNumber(ktFile.text, annotationEntry.startOffset) + 1
experimentalAnnotations.map { annotation ->
ExperimentalAnnotationUsage(file, lineNumber, annotation)
}
}
}
private fun Path.parseAsKtFile(project: Project): KtFile {
return PsiFileFactoryImpl(project).createFileFromText(name, KotlinLanguage.INSTANCE, readText()) as KtFile
}
private fun createProjectForParsing(): Project {
return KotlinCoreEnvironment.createForProduction(
Disposer.newDisposable(),
CompilerConfiguration(),
EnvironmentConfigFiles.JVM_CONFIG_FILES
).project
}
private const val OPT_IN_ANNOTATION = "OptIn"
}
@@ -0,0 +1,59 @@
/*
* 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.ide.plugin.dependencies.validator
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.absolute
import kotlin.io.path.relativeTo
fun main(args: Array<String>) {
check(args.isNotEmpty())
val experimentalAnnotationUsages =
ExperimentalOptInUsageInSourceChecker.checkExperimentalOptInUsage(args.map { Paths.get(it) })
.filterNot { usage -> allowedUsages.any { it.isAllowed(usage) } }
if (experimentalAnnotationUsages.isNotEmpty()) {
val errorMessage = buildString {
appendLine(
"""
Experimental Kotlin StdLib API cannot be used in the modules that are used in the IDE.
See KT-62510 for more details.
The following files use deprecated APIs:
""".trimIndent()
)
for (annotationUsage in experimentalAnnotationUsages) {
appendLine(
" - " + annotationUsage.file.relativeTo(Paths.get(".").toAbsolutePath()).toString() +
":" + annotationUsage.lineNumber +
" has an opt-in for experimental declaration: @OptIn(${annotationUsage.usedExperimentalAnnotation}::class)"
)
}
}
error(errorMessage)
}
}
// Please do not add new usages here, it may break IntelliJ Kotlin Plugin. See KT-62510 for more details
private val allowedUsages = listOf(
// TODO should be removed as a part of KTIJ-27368
AllowedUsage(
Paths.get("compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/CityHash.kt"),
"ExperimentalUnsignedTypes"
)
)
private data class AllowedUsage(
val file: Path,
val usedExperimentalAnnotation: String,
) {
fun isAllowed(usage: ExperimentalAnnotationUsage) =
usage.file.absolute() == file.absolute()
&& usedExperimentalAnnotation == usage.usedExperimentalAnnotation
}
@@ -0,0 +1,29 @@
@OptIn(ExperimentalStdlibApi::class)
class Usage {
}
@OptIn(ExperimentalStdlibApi::class, OtherOptIn::class)
class WithOtherArgument {
}
fun x() {
@OptIn(ExperimentalStdlibApi::class)
call()
}
@OptIn()
fun empty() {
}
@ExperimentalStdlibApi
fun direct() {
}
@kotlin.io.path.ExperimentalPathApi
fun fqName() {
}
@@ -0,0 +1,18 @@
@OptIn(ExperimentalPathApi::class, ExperimentalStdlibApi::class)
class Usage {
}
@ExperimentalPathApi
@ExperimentalStdlibApi
fun direct() {
}
@kotlin.io.path.ExperimentalPathApi
@kotlin.io.path.ExperimentalPathApi
fun fqName() {
}
@@ -0,0 +1,30 @@
@OptIn(ExperimentalPathApi::class)
class Usage {
}
@OptIn(ExperimentalPathApi::class, OtherOptIn::class)
class WithOtherArgument {
}
fun x() {
@OptIn(ExperimentalPathApi::class)
call()
}
@OptIn()
fun y() {
}
@ExperimentalPathApi
fun direct() {
}
@kotlin.io.path.ExperimentalPathApi
fun fqName() {
}
@@ -0,0 +1,50 @@
/*
* 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.ide.plugin.dependencies.validator
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.nio.file.Paths
import kotlin.io.path.absolute
class ExperimentalOptInUsageInSourceCheckerTest {
@Test
fun test() {
println(Paths.get(".").toAbsolutePath())
val sourcePath = basePath.resolve("testData/source")
val usages = ExperimentalOptInUsageInSourceChecker.checkExperimentalOptInUsage(listOf(sourcePath))
Assertions.assertEquals(
listOf(
ExperimentalAnnotationUsage(Paths.get("pckg/experimentalPathApi.kt"), lineNumber = 1, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("pckg/experimentalPathApi.kt"), lineNumber = 6, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("pckg/experimentalPathApi.kt"), lineNumber = 12, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("pckg/experimentalPathApi.kt"), lineNumber = 21, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("pckg/experimentalPathApi.kt"), lineNumber = 27, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("experimentalStdlibApi.kt"), lineNumber = 1, "ExperimentalStdlibApi"),
ExperimentalAnnotationUsage(Paths.get("experimentalStdlibApi.kt"), lineNumber = 6, "ExperimentalStdlibApi"),
ExperimentalAnnotationUsage(Paths.get("experimentalStdlibApi.kt"), lineNumber = 12, "ExperimentalStdlibApi"),
ExperimentalAnnotationUsage(Paths.get("experimentalStdlibApi.kt"), lineNumber = 21, "ExperimentalStdlibApi"),
ExperimentalAnnotationUsage(Paths.get("experimentalStdlibApi.kt"), lineNumber = 26, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("multiple.kt"), lineNumber = 2, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("multiple.kt"), lineNumber = 2, "ExperimentalStdlibApi"),
ExperimentalAnnotationUsage(Paths.get("multiple.kt"), lineNumber = 7, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("multiple.kt"), lineNumber = 8, "ExperimentalStdlibApi"),
ExperimentalAnnotationUsage(Paths.get("multiple.kt"), lineNumber = 14, "ExperimentalPathApi"),
ExperimentalAnnotationUsage(Paths.get("multiple.kt"), lineNumber = 15, "ExperimentalPathApi")
).map { it.copy(file = sourcePath.resolve(it.file)) }.sortedWith(experimentalAnnotationUsageComparator),
usages.sortedWith(experimentalAnnotationUsageComparator),
)
}
companion object {
private val experimentalAnnotationUsageComparator =
compareBy<ExperimentalAnnotationUsage>(
{ it.file.absolute().toString() },
{ it.lineNumber },
{ it.usedExperimentalAnnotation },
)
}
}
@@ -0,0 +1,10 @@
/*
* 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.ide.plugin.dependencies.validator
import java.nio.file.Paths
val basePath = Paths.get("libraries/tools/ide-plugin-dependencies-validator")
@@ -114,6 +114,9 @@ fun Project.configureJavaBasePlugin() {
} }
} }
val projectsUsedInIntelliJKotlinPlugin: Array<String> by rootProject.extra
val kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin: String by rootProject.extra
fun Project.configureKotlinCompilationOptions() { fun Project.configureKotlinCompilationOptions() {
plugins.withType<KotlinBasePluginWrapper> { plugins.withType<KotlinBasePluginWrapper> {
val commonCompilerArgs = listOfNotNull( val commonCompilerArgs = listOfNotNull(
@@ -152,6 +155,9 @@ fun Project.configureKotlinCompilationOptions() {
apiVersion = kotlinLanguageVersion apiVersion = kotlinLanguageVersion
freeCompilerArgs += "-Xskip-prerelease-check" freeCompilerArgs += "-Xskip-prerelease-check"
} }
if (project.path in projectsUsedInIntelliJKotlinPlugin) {
apiVersion = kotlinApiVersionForProjectsUsedInIntelliJKotlinPlugin
}
if (KotlinVersion.DEFAULT >= KotlinVersion.KOTLIN_2_0 && forced19) { if (KotlinVersion.DEFAULT >= KotlinVersion.KOTLIN_2_0 && forced19) {
options.progressiveMode.set(false) options.progressiveMode.set(false)
} }
@@ -300,6 +300,14 @@ fun Project.idePluginDependency(block: () -> Unit) {
} }
fun Project.publishJarsForIde(projects: List<String>, libraryDependencies: List<String> = emptyList()) { fun Project.publishJarsForIde(projects: List<String>, libraryDependencies: List<String> = emptyList()) {
val projectsUsedInIntelliJKotlinPlugin: Array<String> by rootProject.extra
for (projectName in projects) {
check(projectName in projectsUsedInIntelliJKotlinPlugin) {
"`$projectName` is used in IntelliJ Kotlin Plugin, it should be added to `extra[\"projectsUsedInIntelliJKotlinPlugin\"]`"
}
}
idePluginDependency { idePluginDependency {
publishProjectJars(projects, libraryDependencies) publishProjectJars(projects, libraryDependencies)
} }
+3 -1
View File
@@ -575,7 +575,8 @@ include ":generators:analysis-api-generator",
":analysis:decompiled:native", ":analysis:decompiled:native",
":analysis:decompiled:light-classes-for-decompiled", ":analysis:decompiled:light-classes-for-decompiled",
":analysis:decompiled:light-classes-for-decompiled-fe10", ":analysis:decompiled:light-classes-for-decompiled-fe10",
":prepare:analysis-api-test-framework" ":prepare:analysis-api-test-framework",
":tools:ide-plugin-dependencies-validator"
if (buildProperties.inJpsBuildIdeaSync) { if (buildProperties.inJpsBuildIdeaSync) {
@@ -615,6 +616,7 @@ if (buildProperties.inJpsBuildIdeaSync) {
project(':tools:binary-compatibility-validator').projectDir = "$rootDir/libraries/tools/binary-compatibility-validator" as File project(':tools:binary-compatibility-validator').projectDir = "$rootDir/libraries/tools/binary-compatibility-validator" as File
project(':tools:jdk-api-validator').projectDir = "$rootDir/libraries/tools/jdk-api-validator" as File project(':tools:jdk-api-validator').projectDir = "$rootDir/libraries/tools/jdk-api-validator" as File
project(':tools:kotlin-stdlib-gen').projectDir = "$rootDir/libraries/tools/kotlin-stdlib-gen" as File project(':tools:kotlin-stdlib-gen').projectDir = "$rootDir/libraries/tools/kotlin-stdlib-gen" as File
project(':tools:ide-plugin-dependencies-validator').projectDir = "$rootDir/libraries/tools/ide-plugin-dependencies-validator" as File
project(':kotlin-test').projectDir = "$rootDir/libraries/kotlin.test" as File project(':kotlin-test').projectDir = "$rootDir/libraries/kotlin.test" as File
project(':kotlin-test:kotlin-test-js-ir').projectDir = "$rootDir/libraries/kotlin.test/js-ir" as File project(':kotlin-test:kotlin-test-js-ir').projectDir = "$rootDir/libraries/kotlin.test/js-ir" as File